Creating an online job portal is a multifaceted project that demands meticulous planning and execution. Both job seekers and employers expect a seamless, feature-rich experience that facilitates job hunting and recruitment processes. This article outlines a modular approach to building the backend of an online job portal using Node.js and Express, covering everything from initial setup to deployment.
Module 1: Project Setup
Setup Development Environment
To begin, install Node.js and Visual Studio Code. Setup Git and create a GitHub repository for version control. This ensures you can track changes and collaborate efficiently.
Initialize Project
Create a project directory and initialize it with npm init
to generate package.json
. Configure ESLint and Prettier to maintain code quality and consistency.
Install Dependencies
Install essential packages:
- Express for server-side framework
- Mongoose for MongoDB interactions
- Bcrypt for password hashing
- JWT for authentication
- Multer for file uploads
- Dotenv for managing environment variables
- Nodemon for automatic server restarts during development
Create Project Structure
Organize the project into directories for routes, controllers, models, middleware, and configuration files.
Setup Server
Create a basic Express server, configure environment variables, and establish a connection to MongoDB. Set up initial routes and middleware to ensure the server runs smoothly.
Module 2: User Management
User Types and Registration
Define user types (job seekers and recruiters) and set up registration and login routes. Use Mongoose to create UserType
and UserAccount
models. Implement controllers for user authentication and profile management.
Authentication
Implement JWT-based authentication for secure user sessions. Use Bcrypt to hash passwords. Ensure robust validation for registration and login data.
Profile Management
Develop models for UserProfile
and CompanyProfile
. Allow users to upload profile images using Multer. Create routes and controllers to manage profile data.
User Logs
Create a UserLog
model to track user login and job application dates. This data can drive features like recommending resume-building services to active job seekers.
Module 3: Job Posting and Management
Job Posting
Develop models for JobPost
, Company
, JobPostSkillSet
, and JobLocation
. Create routes and controllers to handle job posting, updating, and deletion. Allow recruiters to post jobs and manage job listings.
Skill Sets
Implement a SkillSet
model to manage skills required for job posts and possessed by job seekers. Ensure the system can handle multiple skills per user and job post.
Job Search and Filtering
Build robust search functionality with filters for location, skills, salary, experience, and more. Implement pagination to handle large datasets efficiently.
Module 4: Job Application Management
Job Application
Create a JobPostActivity
model to track job applications. Develop routes and controllers for job seekers to apply for jobs and recruiters to manage applications.
Real-time Updates
Optionally, use WebSockets to implement real-time notifications for job applications, keeping users informed instantly.
Module 5: Dashboard and Reporting
User Dashboards
Implement personal dashboards for job seekers and recruiters. Dashboards should display job applications, saved jobs, and other relevant data.
Salary Reports
Develop routes and controllers to generate salary reports by job roles, companies, industries, and locations. These reports can help users understand market trends.
Module 6: Additional Features
LinkedIn and Social Media Integration
Allow users to link their LinkedIn profiles, enhancing their visibility and networking capabilities.
Resume Builder
Provide a form for users to build resumes directly on the portal. Offer a library of resume examples for inspiration.
Notifications
Manage user preferences for email and SMS notifications. Implement a notification system to keep users informed about relevant updates.
Module 7: Error Handling and Validation
Error Handling
Create centralized error handling middleware. Develop custom error classes to manage different types of errors effectively.
Validation
Implement comprehensive input validation for all routes using middleware. This ensures data integrity and enhances security.
Module 8: Testing and Documentation
Testing
Write unit and integration tests using frameworks like Mocha, Chai, and Supertest. Ensure all functionalities are thoroughly tested.
Documentation
Document APIs using tools like Swagger. Provide a comprehensive README and setup guides to assist developers in understanding and using the project.
Module 9: Deployment and Maintenance
Deployment
Deploy the application to cloud platforms like Heroku, AWS, or DigitalOcean. Set up CI/CD pipelines for automated deployment and testing.
Maintenance
Monitor application performance and address issues promptly. Plan regular updates and feature enhancements to keep the portal competitive and user-friendly.
Project Structure
Organizing your project is crucial for maintainability. Here’s a recommended structure:
job-portal/
├── config/
│ └── db.js
├── controllers/
│ ├── authController.js
│ ├── profileController.js
│ ├── jobController.js
│ └── applicationController.js
├── middlewares/
│ ├── authMiddleware.js
│ ├── errorMiddleware.js
│ └── validationMiddleware.js
├── models/
│ ├── UserType.js
│ ├── UserAccount.js
│ ├── UserProfile.js
│ ├── CompanyProfile.js
│ ├── JobPost.js
│ ├── JobPostActivity.js
│ ├── SkillSet.js
│ └── JobLocation.js
├── routes/
│ ├── authRoutes.js
│ ├── profileRoutes.js
│ ├── jobRoutes.js
│ └── applicationRoutes.js
├── utils/
│ ├── logger.js
│ └── responseHandler.js
├── .env
├── .eslintrc.js
├── .prettierrc
├── package.json
├── server.js
└── README.md
Example ER Diagram
Visualize your database relationships using an ER diagram. Tools like draw.io or Lucidchart can help you create detailed diagrams. Here’s an outline of the tables:
- UserType: id, description
- UserAccount: id, user_type_id, email, password, date_of_birth, gender, is_active, contact_number, sms_notification_active, email_notification_active, user_image, registration_date
- UserLog: user_account_id, last_login_date, last_application_date
- UserProfile: user_account_id, first_name, last_name, current_salary, is_annually_monthly, currency
- CompanyProfile: id, company_name, profile_description, business_stream_id, establishment_date, company_website_url
- JobPost: id, posted_by_id, job_type_id, company_id, is_company_name_hidden, created_date, job_description, job_location_id, is_active
- JobPostSkillSet: job_post_id, skill_set_id
- JobPostActivity: job_post_id, user_account_id, application_date
- SkillSet: id, skill_set_name
- JobLocation: id, street_address, city, state, country, postal_code
- EducationDetail: user_account_id, certificate_degree_name, major, institute_university_name, start_date, completion_date, percentage, cgpa
- ExperienceDetail: user_account_id, is_current_job, start_date, end_date, job_title, company_name, job_location_city, job_location_state, job_location_country, description
- SeekerSkillSet: user_account_id, skill_set_id, skill_level
- BusinessStream: id, business_stream_name
By following this structured approach, you can develop a robust, scalable, and feature-rich online job portal. Each module can be developed and tested independently, ensuring a seamless integration and a high-quality end product.
Module 1: Project Setup
The first module is dedicated to setting up the development environment, initializing the project, installing necessary dependencies, and creating the project structure. This foundational step ensures that the project is well-organized and ready for further development.
Step 1: Setup Development Environment
Install Node.js and NPM:
- Download and install Node.js from the official website.
- Verify the installation by running
node -v
npm -v
3.
Install Visual Studio Code:
- Download and install Visual Studio Code from the official website.
Setup Git:
- Download and install Git from the official website.
- Configure Git with your user name and email
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
4. Create GitHub Repository:
- Go to GitHub and create a new repository for your project.
- Clone the repository to your local machine
git clone https://github.com/yourusername/your-repo-name.git
cd your-repo-name
Step 2: Initialize Project
Create Project Directory:
mkdir job-portal
cd job-portal
2. Initialize package.json
:
npm init -y
3. Setup ESLint and Prettier:
Install ESLint and Prettier:
npm install eslint prettier eslint-config-prettier eslint-plugin-prettier --save-dev
Initialize ESLint:
npx eslint --init
Create .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
rules: {},
};
Create .prettierrc
:
{
"singleQuote": true,
"semi": false
}
Step 3: Install Dependencies
Install Core Dependencies:
npm install express mongoose bcryptjs jsonwebtoken multer dotenv
Install Development Dependencies:
npm install --save-dev nodemon
Add Scripts to package.json
:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
Step 4: Create Project Structure
Create the following directory structure to organize your project:
job-portal/
├── config/
│ └── db.js
├── controllers/
│ ├── authController.js
│ ├── profileController.js
│ ├── jobController.js
│ └── applicationController.js
├── middlewares/
│ ├── authMiddleware.js
│ ├── errorMiddleware.js
│ └── validationMiddleware.js
├── models/
│ ├── UserType.js
│ ├── UserAccount.js
│ ├── UserProfile.js
│ ├── CompanyProfile.js
│ ├── JobPost.js
│ ├── JobPostActivity.js
│ ├── SkillSet.js
│ └── JobLocation.js
├── routes/
│ ├── authRoutes.js
│ ├── profileRoutes.js
│ ├── jobRoutes.js
│ └── applicationRoutes.js
├── utils/
│ ├── logger.js
│ └── responseHandler.js
├── .env
├── .eslintrc.js
├── .prettierrc
├── package.json
├── server.js
└── README.md
Step 5: Setup Server
Create
server.js
:
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Middleware
app.use(express.json());
// Routes
app.get('/', (req, res) => {
res.send('Job Portal API');
});
// Connect to MongoDB
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch((err) => console.log(err));
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Create .env
File:
MONGO_URI=your_mongodb_connection_string
PORT=5000
JWT_SECRET=your_jwt_secret
Create config/db.js
:
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB connected');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
module.exports = connectDB;
Update server.js
to Use connectDB
:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
// Routes
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 6: Connect to GitHub
Initialize Git Repository:
git init
git add .
git commit -m "Initial commit"
Push to GitHub:
git remote add origin https://github.com/yourusername/your-repo-name.git
git push -u origin master
By completing Module 1, you have set up a robust development environment for your online job portal. This setup includes initializing the project, installing essential dependencies, creating a clear project structure, and establishing a connection to MongoDB. With this foundation, you are ready to proceed with building the core functionalities of your job portal. Each subsequent module will build on this groundwork, progressively adding features and ensuring a scalable, maintainable application.
Module 2: User Management
In this module, we will focus on setting up user management, including user registration, login, profile management, and logging user activities. This involves creating the necessary models, routes, controllers, and middleware to handle authentication and user-related operations.
Step 1: Define User Models
Create
models/UserType.js
:
const mongoose = require('mongoose');
const UserTypeSchema = new mongoose.Schema({
type: {
type: String,
required: true,
},
});
module.exports = mongoose.model('UserType', UserTypeSchema);
Create models/UserAccount.js
:
const mongoose = require('mongoose');
const UserAccountSchema = new mongoose.Schema({
userType: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserType',
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
dateOfBirth: {
type: Date,
},
gender: {
type: String,
},
isActive: {
type: Boolean,
default: true,
},
contactNumber: {
type: String,
},
smsNotificationActive: {
type: Boolean,
default: true,
},
emailNotificationActive: {
type: Boolean,
default: true,
},
userImage: {
type: String,
},
registrationDate: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('UserAccount', UserAccountSchema);
Create models/UserLog.js
:
const mongoose = require('mongoose');
const UserLogSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserAccount',
required: true,
},
lastLoginDate: {
type: Date,
},
lastApplicationDate: {
type: Date,
},
});
module.exports = mongoose.model('UserLog', UserLogSchema);
Step 2: Implement User Authentication
Create
controllers/authController.js
:
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const UserAccount = require('../models/UserAccount');
const UserLog = require('../models/UserLog');
const UserType = require('../models/UserType');
// User Registration
exports.registerUser = async (req, res) => {
const { email, password, userType, dateOfBirth, gender, contactNumber } = req.body;
try {
let user = await UserAccount.findOne({ email });
if (user) {
return res.status(400).json({ msg: 'User already exists' });
}
user = new UserAccount({
email,
password,
userType,
dateOfBirth,
gender,
contactNumber,
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// User Login
exports.loginUser = async (req, res) => {
const { email, password } = req.body;
try {
let user = await UserAccount.findOne({ email });
if (!user) {
return res.status(400).json({ msg: 'Invalid Credentials' });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ msg: 'Invalid Credentials' });
}
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
await UserLog.findOneAndUpdate(
{ user: user.id },
{ lastLoginDate: new Date() },
{ upsert: true }
);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get User Info
exports.getUserInfo = async (req, res) => {
try {
const user = await UserAccount.findById(req.user.id).select('-password');
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
Create routes/authRoutes.js
:
const express = require('express');
const router = express.Router();
const { check, validationResult } = require('express-validator');
const authController = require('../controllers/authController');
const authMiddleware = require('../middlewares/authMiddleware');
// User Registration
router.post(
'/register',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
],
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
authController.registerUser
);
// User Login
router.post(
'/login',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
],
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
authController.loginUser
);
// Get User Info
router.get('/me', authMiddleware, authController.getUserInfo);
module.exports = router;
Create middlewares/authMiddleware.js
:
const jwt = require('jsonwebtoken');
module.exports = function (req, res, next) {
// Get token from header
const token = req.header('x-auth-token');
// Check if not token
if (!token) {
return res.status(401).json({ msg: 'No token, authorization denied' });
}
// Verify token
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: 'Token is not valid' });
}
};
Step 3: Profile Management
Create
models/UserProfile.js
:
const mongoose = require('mongoose');
const UserProfileSchema = new mongoose.Schema({
userAccount: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserAccount',
required: true,
},
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
currentSalary: {
type: Number,
},
isAnnuallyMonthly: {
type: String,
},
currency: {
type: String,
},
});
module.exports = mongoose.model('UserProfile', UserProfileSchema);
2. Create controllers/profileController.js
:
const UserProfile = require('../models/UserProfile');
// Get Profile
exports.getProfile = async (req, res) => {
try {
const profile = await UserProfile.findOne({ userAccount: req.user.id });
if (!profile) {
return res.status(400).json({ msg: 'Profile not found' });
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Create or Update Profile
exports.upsertProfile = async (req, res) => {
const { firstName, lastName, currentSalary, isAnnuallyMonthly, currency } = req.body;
const profileFields = {
userAccount: req.user.id,
firstName,
lastName,
currentSalary,
isAnnuallyMonthly,
currency,
};
try {
let profile = await UserProfile.findOneAndUpdate(
{ userAccount: req.user.id },
{ $set: profileFields },
{ new: true, upsert: true }
);
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
Create routes/profileRoutes.js
:
heroku create job-portal
const express = require('express');
const router = express.Router();
const profileController = require('../controllers/profileController');
const authMiddleware = require('../middlewares/authMiddleware');
// Get Profile
router.get('/', authMiddleware, profileController.getProfile);
// Create or Update Profile
router.post('/', authMiddleware, profileController.upsertProfile);
module.exports = router;
Step 4: Update Server to Include New Routes
- Update
server.js
:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Module 2 sets up user management functionality, including user registration, login, and profile management. We have created the necessary models, routes, and controllers to handle these operations, as well as middleware for authentication. This forms the basis for managing user interactions within the job portal. Subsequent modules will build on this foundation to add more features and functionalities.
Module 3; Job Posting and Management
In this module, we will focus on implementing job posting and management functionalities. This includes creating models for job posts, companies, job locations, and job post skill sets. We will also develop the necessary routes and controllers to handle job-related operations such as posting a job, updating a job post, deleting a job post, and retrieving job posts.
Step 1: Define Job and Company Models
Create
models/CompanyProfile.js
:
const mongoose = require('mongoose');
const CompanyProfileSchema = new mongoose.Schema({
companyName: {
type: String,
required: true,
},
profileDescription: {
type: String,
},
businessStream: {
type: mongoose.Schema.Types.ObjectId,
ref: 'BusinessStream',
},
establishmentDate: {
type: Date,
},
companyWebsiteUrl: {
type: String,
required: true,
},
});
module.exports = mongoose.model('CompanyProfile', CompanyProfileSchema);
Create models/JobPost.js
:
const mongoose = require('mongoose');
const JobPostSchema = new mongoose.Schema({
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserAccount',
required: true,
},
company: {
type: mongoose.Schema.Types.ObjectId,
ref: 'CompanyProfile',
required: true,
},
jobType: {
type: String,
enum: ['Permanent', 'Temporary'],
required: true,
},
isCompanyNameHidden: {
type: Boolean,
default: false,
},
createdDate: {
type: Date,
default: Date.now,
},
jobDescription: {
type: String,
required: true,
},
jobLocation: {
type: mongoose.Schema.Types.ObjectId,
ref: 'JobLocation',
required: true,
},
isActive: {
type: Boolean,
default: true,
},
});
module.exports = mongoose.model('JobPost', JobPostSchema);
Create models/JobLocation.js
:
const mongoose = require('mongoose');
const JobLocationSchema = new mongoose.Schema({
streetAddress: {
type: String,
},
city: {
type: String,
required: true,
},
state: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
postalCode: {
type: String,
},
});
module.exports = mongoose.model('JobLocation', JobLocationSchema);
Create models/JobPostSkillSet.js
:
const mongoose = require('mongoose');
const JobPostSkillSetSchema = new mongoose.Schema({
jobPost: {
type: mongoose.Schema.Types.ObjectId,
ref: 'JobPost',
required: true,
},
skillSet: {
type: mongoose.Schema.Types.ObjectId,
ref: 'SkillSet',
required: true,
},
});
module.exports = mongoose.model('JobPostSkillSet', JobPostSkillSetSchema);
Create models/SkillSet.js
:
const mongoose = require('mongoose');
const SkillSetSchema = new mongoose.Schema({
skillSetName: {
type: String,
required: true,
},
});
module.exports = mongoose.model('SkillSet', SkillSetSchema);
Step 2: Implement Job Posting and Management
Create
controllers/jobController.js
:
const JobPost = require('../models/JobPost');
const JobLocation = require('../models/JobLocation');
const CompanyProfile = require('../models/CompanyProfile');
const JobPostSkillSet = require('../models/JobPostSkillSet');
const SkillSet = require('../models/SkillSet');
// Create Job Post
exports.createJobPost = async (req, res) => {
const { company, jobType, isCompanyNameHidden, jobDescription, jobLocation, skillSets } = req.body;
try {
const location = new JobLocation(jobLocation);
await location.save();
const jobPost = new JobPost({
postedBy: req.user.id,
company,
jobType,
isCompanyNameHidden,
jobDescription,
jobLocation: location._id,
});
await jobPost.save();
for (const skill of skillSets) {
const skillSet = new JobPostSkillSet({
jobPost: jobPost._id,
skillSet: skill,
});
await skillSet.save();
}
res.json(jobPost);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get All Job Posts
exports.getAllJobPosts = async (req, res) => {
try {
const jobPosts = await JobPost.find()
.populate('postedBy', ['email'])
.populate('company', ['companyName', 'profileDescription'])
.populate('jobLocation', ['city', 'state', 'country'])
.populate('skillSets', ['skillSetName']);
res.json(jobPosts);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Job Post by ID
exports.getJobPostById = async (req, res) => {
try {
const jobPost = await JobPost.findById(req.params.id)
.populate('postedBy', ['email'])
.populate('company', ['companyName', 'profileDescription'])
.populate('jobLocation', ['city', 'state', 'country'])
.populate('skillSets', ['skillSetName']);
if (!jobPost) {
return res.status(404).json({ msg: 'Job post not found' });
}
res.json(jobPost);
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(404).json({ msg: 'Job post not found' });
}
res.status(500).send('Server error');
}
};
// Update Job Post
exports.updateJobPost = async (req, res) => {
const { jobType, isCompanyNameHidden, jobDescription, jobLocation, skillSets } = req.body;
try {
let jobPost = await JobPost.findById(req.params.id);
if (!jobPost) {
return res.status(404).json({ msg: 'Job post not found' });
}
// Update Job Location
let location = await JobLocation.findById(jobPost.jobLocation);
if (location) {
location.streetAddress = jobLocation.streetAddress || location.streetAddress;
location.city = jobLocation.city || location.city;
location.state = jobLocation.state || location.state;
location.country = jobLocation.country || location.country;
location.postalCode = jobLocation.postalCode || location.postalCode;
await location.save();
}
// Update Job Post
jobPost.jobType = jobType || jobPost.jobType;
jobPost.isCompanyNameHidden = isCompanyNameHidden || jobPost.isCompanyNameHidden;
jobPost.jobDescription = jobDescription || jobPost.jobDescription;
jobPost.jobLocation = location._id || jobPost.jobLocation;
await jobPost.save();
// Update Skill Sets
await JobPostSkillSet.deleteMany({ jobPost: jobPost._id });
for (const skill of skillSets) {
const skillSet = new JobPostSkillSet({
jobPost: jobPost._id,
skillSet: skill,
});
await skillSet.save();
}
res.json(jobPost);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Delete Job Post
exports.deleteJobPost = async (req, res) => {
try {
const jobPost = await JobPost.findById(req.params.id);
if (!jobPost) {
return res.status(404).json({ msg: 'Job post not found' });
}
await JobLocation.findByIdAndDelete(jobPost.jobLocation);
await JobPostSkillSet.deleteMany({ jobPost: jobPost._id });
await jobPost.remove();
res.json({ msg: 'Job post removed' });
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(404).json({ msg: 'Job post not found' });
}
res.status(500).send('Server error');
}
};
Create routes/jobRoutes.js
:
const express = require('express');
const router = express.Router();
const jobController = require('../controllers/jobController');
const authMiddleware = require('../middlewares/authMiddleware');
// Create Job Post
router.post('/', authMiddleware, jobController.createJobPost);
// Get All Job Posts
router.get('/', jobController.getAllJobPosts);
// Get Job Post by ID
router.get('/:id', jobController.getJobPostById);
// Update Job Post
router.put('/:id', authMiddleware, jobController.updateJobPost);
// Delete Job Post
router.delete('/:id', authMiddleware, jobController.deleteJobPost);
module.exports = router;
Step 3: Update Server to Include Job Routes
- Update
server.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Module 3 establishes the foundation for job posting and management within the online job portal. It includes models for job posts, companies, job locations, and skill sets, as well as routes and controllers to handle job-related operations. This module enables recruiters to post jobs, update job posts, and delete job posts, while job seekers can view and apply for jobs. Subsequent modules will build on this functionality to enhance the overall user experience.
Module 4: Job Application Management
In this module, we will focus on implementing job application management functionalities. This includes creating models for job applications, developing routes and controllers to handle job application operations, and allowing job seekers to apply for jobs and recruiters to manage applications.
Step 1: Define Job Application Models
- Create
models/JobPostActivity.js
const mongoose = require('mongoose');
const JobPostActivitySchema = new mongoose.Schema({
jobPost: {
type: mongoose.Schema.Types.ObjectId,
ref: 'JobPost',
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserAccount',
required: true,
},
applicationDate: {
type: Date,
default: Date.now,
},
status: {
type: String,
enum: ['Applied', 'Viewed', 'Interviewing', 'Hired', 'Rejected'],
default: 'Applied',
},
});
module.exports = mongoose.model('JobPostActivity', JobPostActivitySchema);
Step 2: Implement Job Application Management
Create
controllers/applicationController.js
:
const JobPostActivity = require('../models/JobPostActivity');
const JobPost = require('../models/JobPost');
// Apply for Job
exports.applyForJob = async (req, res) => {
const { jobPostId } = req.body;
try {
const jobPost = await JobPost.findById(jobPostId);
if (!jobPost) {
return res.status(404).json({ msg: 'Job post not found' });
}
const application = new JobPostActivity({
jobPost: jobPostId,
user: req.user.id,
});
await application.save();
res.json(application);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Applications for a Job Post
exports.getApplicationsForJob = async (req, res) => {
try {
const applications = await JobPostActivity.find({ jobPost: req.params.jobPostId })
.populate('user', ['email'])
.populate('jobPost', ['jobDescription']);
res.json(applications);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Applications for a User
exports.getUserApplications = async (req, res) => {
try {
const applications = await JobPostActivity.find({ user: req.user.id })
.populate('jobPost', ['jobDescription']);
res.json(applications);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Update Application Status
exports.updateApplicationStatus = async (req, res) => {
const { status } = req.body;
try {
let application = await JobPostActivity.findById(req.params.id);
if (!application) {
return res.status(404).json({ msg: 'Application not found' });
}
application.status = status;
await application.save();
res.json(application);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
Create routes/applicationRoutes.js
:
const express = require('express');
const router = express.Router();
const applicationController = require('../controllers/applicationController');
const authMiddleware = require('../middlewares/authMiddleware');
// Apply for Job
router.post('/', authMiddleware, applicationController.applyForJob);
// Get Applications for a Job Post
router.get('/job/:jobPostId', authMiddleware, applicationController.getApplicationsForJob);
// Get Applications for a User
router.get('/user', authMiddleware, applicationController.getUserApplications);
// Update Application Status
router.put('/:id', authMiddleware, applicationController.updateApplicationStatus);
module.exports = router;
Step 3: Update Server to Include Application Routes
- Update
server.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Module 4 adds crucial job application management functionality to the online job portal. It includes creating models for job applications and developing routes and controllers to handle applying for jobs, retrieving applications, and updating application statuses. This module enables job seekers to apply for jobs and recruiters to manage applications, making the job portal more functional and user-friendly. Subsequent modules will build on this foundation to further enhance the portal’s capabilities.
Module 5: Dashboard and Reporting
In this module, we will implement personal dashboards for both job seekers and recruiters. We will also add salary reporting functionality by job roles, companies, industries, and locations. This will help users track their job applications, saved jobs, and provide insights into salary trends.
Step 1: Implement User Dashboards
Create
controllers/dashboardController.js
:
const JobPostActivity = require('../models/JobPostActivity');
const JobPost = require('../models/JobPost');
const UserProfile = require('../models/UserProfile');
const CompanyProfile = require('../models/CompanyProfile');
// Get Dashboard Data for Job Seeker
exports.getJobSeekerDashboard = async (req, res) => {
try {
const applications = await JobPostActivity.find({ user: req.user.id })
.populate('jobPost', ['jobDescription', 'company'])
.populate('user', ['email']);
res.json({ applications });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Dashboard Data for Recruiter
exports.getRecruiterDashboard = async (req, res) => {
try {
const company = await CompanyProfile.findOne({ user: req.user.id });
if (!company) {
return res.status(404).json({ msg: 'Company not found' });
}
const jobPosts = await JobPost.find({ company: company.id })
.populate('company', ['companyName'])
.populate('jobLocation', ['city', 'state', 'country']);
const applications = await JobPostActivity.find({ jobPost: { $in: jobPosts.map(j => j.id) } })
.populate('user', ['email'])
.populate('jobPost', ['jobDescription']);
res.json({ jobPosts, applications });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
2.Create routes/dashboardRoutes.js
:
const express = require('express');
const router = express.Router();
const dashboardController = require('../controllers/dashboardController');
const authMiddleware = require('../middlewares/authMiddleware');
// Get Dashboard Data for Job Seeker
router.get('/job-seeker', authMiddleware, dashboardController.getJobSeekerDashboard);
// Get Dashboard Data for Recruiter
router.get('/recruiter', authMiddleware, dashboardController.getRecruiterDashboard);
module.exports = router;
Step 2: Implement Salary Reporting
Create
controllers/reportController.js
:
const JobPost = require('../models/JobPost');
// Get Salary Report by Job Role
exports.getSalaryReportByRole = async (req, res) => {
const { role } = req.query;
try {
const jobPosts = await JobPost.find({ jobDescription: new RegExp(role, 'i') })
.populate('company', ['companyName'])
.populate('jobLocation', ['city', 'state', 'country']);
const salaries = jobPosts.map(post => post.salary);
const averageSalary = salaries.reduce((acc, salary) => acc + salary, 0) / salaries.length;
res.json({ role, averageSalary, jobPosts });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Salary Report by Company
exports.getSalaryReportByCompany = async (req, res) => {
const { companyId } = req.query;
try {
const jobPosts = await JobPost.find({ company: companyId })
.populate('company', ['companyName'])
.populate('jobLocation', ['city', 'state', 'country']);
const salaries = jobPosts.map(post => post.salary);
const averageSalary = salaries.reduce((acc, salary) => acc + salary, 0) / salaries.length;
res.json({ companyId, averageSalary, jobPosts });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Salary Report by Industry
exports.getSalaryReportByIndustry = async (req, res) => {
const { industry } = req.query;
try {
const companies = await CompanyProfile.find({ businessStream: new RegExp(industry, 'i') });
const companyIds = companies.map(c => c.id);
const jobPosts = await JobPost.find({ company: { $in: companyIds } })
.populate('company', ['companyName'])
.populate('jobLocation', ['city', 'state', 'country']);
const salaries = jobPosts.map(post => post.salary);
const averageSalary = salaries.reduce((acc, salary) => acc + salary, 0) / salaries.length;
res.json({ industry, averageSalary, jobPosts });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
// Get Salary Report by Location
exports.getSalaryReportByLocation = async (req, res) => {
const { location } = req.query;
try {
const jobPosts = await JobPost.find({ 'jobLocation.city': new RegExp(location, 'i') })
.populate('company', ['companyName'])
.populate('jobLocation', ['city', 'state', 'country']);
const salaries = jobPosts.map(post => post.salary);
const averageSalary = salaries.reduce((acc, salary) => acc + salary, 0) / salaries.length;
res.json({ location, averageSalary, jobPosts });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
2.Create routes/reportRoutes.js
:
const express = require('express');
const router = express.Router();
const reportController = require('../controllers/reportController');
const authMiddleware = require('../middlewares/authMiddleware');
// Get Salary Report by Job Role
router.get('/salary/role', authMiddleware, reportController.getSalaryReportByRole);
// Get Salary Report by Company
router.get('/salary/company', authMiddleware, reportController.getSalaryReportByCompany);
// Get Salary Report by Industry
router.get('/salary/industry', authMiddleware, reportController.getSalaryReportByIndustry);
// Get Salary Report by Location
router.get('/salary/location', authMiddleware, reportController.getSalaryReportByLocation);
module.exports = router;
Step 3: Update Server to Include Dashboard and Report Routes
- Update
server.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.use('/api/dashboard', require('./routes/dashboardRoutes'));
app.use('/api/reports', require('./routes/reportRoutes'));
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Module 5 introduces personal dashboards for job seekers and recruiters, allowing them to track their job applications and job posts, respectively. Additionally, it adds salary reporting functionality, enabling users to view average salaries by job roles, companies, industries, and locations. This enhances the overall user experience by providing valuable insights and data visualization, making the job portal more useful and informative. Subsequent modules will continue to build on these features to further refine the portal’s capabilities.
Module 6: Additional Features
In this module, we will implement additional features to enhance the user experience of the job portal. These features include LinkedIn and social media integration, a resume builder, and a comprehensive notification system for email and SMS notifications. These enhancements will make the portal more interactive and user-friendly.
Step 1: LinkedIn and Social Media Integration
LinkedIn Integration: We will use the LinkedIn OAuth 2.0 to allow users to link their LinkedIn profiles to their job seeker accounts.
Install Dependencies:
npm install passport passport-linkedin-oauth2
Create LinkedIn Strategy Configuration:
- Create
config/linkedin.js
const LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
const passport = require('passport');
const UserAccount = require('../models/UserAccount');
passport.use(
new LinkedInStrategy(
{
clientID: process.env.LINKEDIN_CLIENT_ID,
clientSecret: process.env.LINKEDIN_CLIENT_SECRET,
callbackURL: `${process.env.BASE_URL}/api/auth/linkedin/callback`,
scope: ['r_emailaddress', 'r_liteprofile'],
},
async (accessToken, refreshToken, profile, done) => {
const { id, emails, displayName } = profile;
try {
let user = await UserAccount.findOne({ linkedinId: id });
if (user) {
return done(null, user);
}
user = new UserAccount({
linkedinId: id,
email: emails[0].value,
displayName,
});
await user.save();
done(null, user);
} catch (err) {
done(err, false, err.message);
}
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await UserAccount.findById(id);
done(null, user);
} catch (err) {
done(err, false, err.message);
}
});
Update server.js
to Include LinkedIn Authentication:
const passport = require('passport');
require('./config/linkedin');
// Initialize passport
app.use(passport.initialize());
// LinkedIn Authentication Routes
app.get(
'/api/auth/linkedin',
passport.authenticate('linkedin', { state: 'SOME STATE' })
);
app.get(
'/api/auth/linkedin/callback',
passport.authenticate('linkedin', {
successRedirect: '/',
failureRedirect: '/login',
})
);
Step 2: Resume Builder
Create Resume Builder: Implement a basic resume builder that allows users to create and download resumes in PDF format. We will use the
pdfkit
library to generate PDFs.Install Dependencies:
npm install pdfkit
Create controllers/resumeController.js
:
const PDFDocument = require('pdfkit');
exports.generateResume = async (req, res) => {
const { firstName, lastName, email, education, experience, skills } = req.body;
const doc = new PDFDocument();
let buffers = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
let pdfData = Buffer.concat(buffers);
res
.writeHead(200, {
'Content-Length': Buffer.byteLength(pdfData),
'Content-Type': 'application/pdf',
'Content-disposition': 'attachment;filename=resume.pdf',
})
.end(pdfData);
});
doc.fontSize(20).text(`${firstName} ${lastName}`, { align: 'center' });
doc.fontSize(12).text(`Email: ${email}`, { align: 'center' });
doc.moveDown();
doc.fontSize(16).text('Education:');
education.forEach((edu) => {
doc.fontSize(12).text(`${edu.degree}, ${edu.institution} (${edu.startDate} - ${edu.endDate})`);
});
doc.moveDown();
doc.fontSize(16).text('Experience:');
experience.forEach((exp) => {
doc.fontSize(12).text(`${exp.jobTitle}, ${exp.company} (${exp.startDate} - ${exp.endDate})`);
});
doc.moveDown();
doc.fontSize(16).text('Skills:');
doc.fontSize(12).text(skills.join(', '));
doc.end();
};
Create routes/resumeRoutes.js
:
const express = require('express');
const router = express.Router();
const resumeController = require('../controllers/resumeController');
const authMiddleware = require('../middlewares/authMiddleware');
// Generate Resume
router.post('/generate', authMiddleware, resumeController.generateResume);
module.exports = router;
Step 3: Notification System
Setup Email Notifications: We will use
nodemailer
to send email notifications.Install Dependencies:
npm install nodemailer
Create utils/email.js
:
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const sendEmail = (to, subject, text) => {
const mailOptions = {
from: process.env.EMAIL_USER,
to,
subject,
text,
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Email sent: ' + info.response);
});
};
module.exports = sendEmail;
Create controllers/notificationController.js
:
const sendEmail = require('../utils/email');
// Send Notification
exports.sendNotification = async (req, res) => {
const { email, subject, message } = req.body;
try {
sendEmail(email, subject, message);
res.json({ msg: 'Notification sent' });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
Create routes/notificationRoutes.js
:
const express = require('express');
const router = express.Router();
const notificationController = require('../controllers/notificationController');
const authMiddleware = require('../middlewares/authMiddleware');
// Send Notification
router.post('/send', authMiddleware, notificationController.sendNotification);
module.exports = router;
2. Setup SMS Notifications: We will use Twilio for sending SMS notifications.
Install Dependencies:
npm install twilio
Create utils/sms.js
:
const twilio = require('twilio');
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const sendSMS = (to, body) => {
client.messages
.create({
body,
from: process.env.TWILIO_PHONE_NUMBER,
to,
})
.then((message) => console.log(message.sid))
.catch((error) => console.log(error));
};
module.exports = sendSMS;
Update controllers/notificationController.js
:
const sendEmail = require('../utils/email');
const sendSMS = require('../utils/sms');
// Send Notification
exports.sendNotification = async (req, res) => {
const { email, phone, subject, message } = req.body;
try {
if (email) {
sendEmail(email, subject, message);
}
if (phone) {
sendSMS(phone, message);
}
res.json({ msg: 'Notification sent' });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
Step 4: Update Server to Include New Routes
- Update
server.js
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const passport = require('passport');
require('./config/linkedin');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
app.use(passport.initialize());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.use('/api/dashboard', require('./routes/dashboardRoutes'));
app.use('/api/reports', require('./routes/reportRoutes'));
app.use('/api/resume', require('./routes/resumeRoutes'));
app.use('/api/notifications', require('./routes/notificationRoutes'));
// LinkedIn Authentication Routes
app.get('/api/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
app.get(
'/api/auth/linkedin/callback',
passport.authenticate('linkedin', {
successRedirect: '/',
failureRedirect: '/login',
})
);
app.get('/', (req, res) => {
res.send('Job Portal API');
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Module 6 introduces several additional features to enhance the user experience on the job portal. We have integrated LinkedIn for social media linkage, implemented a resume builder to generate PDF resumes, and set up a comprehensive notification system for email and SMS notifications. These enhancements make the portal more interactive and useful for both job seekers and recruiters, thereby increasing user engagement and satisfaction. Subsequent modules can build on these features to further refine and optimize the platform.
Module 7: Error Handling and Validation
In this module, we will focus on implementing robust error handling and input validation mechanisms. This ensures that our application handles errors gracefully and that all incoming data is properly validated, enhancing the overall security and reliability of the job portal.
Step 1: Implement Centralized Error Handling
Create
middlewares/errorMiddleware.js
:
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: err.message });
};
module.exports = errorHandler;
2. Update server.js
to Use the Error Middleware:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const passport = require('passport');
const errorHandler = require('./middlewares/errorMiddleware');
require('./config/linkedin');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
app.use(passport.initialize());
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.use('/api/dashboard', require('./routes/dashboardRoutes'));
app.use('/api/reports', require('./routes/reportRoutes'));
app.use('/api/resume', require('./routes/resumeRoutes'));
app.use('/api/notifications', require('./routes/notificationRoutes'));
// LinkedIn Authentication Routes
app.get('/api/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
app.get(
'/api/auth/linkedin/callback',
passport.authenticate('linkedin', {
successRedirect: '/',
failureRedirect: '/login',
})
);
app.get('/', (req, res) => {
res.send('Job Portal API');
});
// Error Handling Middleware
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Step 2: Implement Input Validation
Install Validation Middleware:
npm install express-validator
Create Validation Middleware: Create middlewares/validationMiddleware.js
:
const { validationResult } = require('express-validator');
const validate = (validations) => {
return async (req, res, next) => {
await Promise.all(validations.map((validation) => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({ errors: errors.array() });
};
};
module.exports = validate;
Apply Validation Middleware to Routes: Update routes/authRoutes.js
:
const express = require('express');
const { check } = require('express-validator');
const router = express.Router();
const authController = require('../controllers/authController');
const authMiddleware = require('../middlewares/authMiddleware');
const validate = require('../middlewares/validationMiddleware');
// User Registration
router.post(
'/register',
validate([
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
]),
authController.registerUser
);
// User Login
router.post(
'/login',
validate([
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
]),
authController.loginUser
);
// Get User Info
router.get('/me', authMiddleware, authController.getUserInfo);
module.exports = router;
Update routes/profileRoutes.js
:
const express = require('express');
const { check } = require('express-validator');
const router = express.Router();
const profileController = require('../controllers/profileController');
const authMiddleware = require('../middlewares/authMiddleware');
const validate = require('../middlewares/validationMiddleware');
// Get Profile
router.get('/', authMiddleware, profileController.getProfile);
// Create or Update Profile
router.post(
'/',
authMiddleware,
validate([
check('firstName', 'First name is required').not().isEmpty(),
check('lastName', 'Last name is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
]),
profileController.upsertProfile
);
module.exports = router;
Update routes/jobRoutes.js
:
const express = require('express');
const { check } = require('express-validator');
const router = express.Router();
const jobController = require('../controllers/jobController');
const authMiddleware = require('../middlewares/authMiddleware');
const validate = require('../middlewares/validationMiddleware');
// Create Job Post
router.post(
'/',
authMiddleware,
validate([
check('company', 'Company is required').not().isEmpty(),
check('jobType', 'Job type is required').not().isEmpty(),
check('jobDescription', 'Job description is required').not().isEmpty(),
check('jobLocation', 'Job location is required').not().isEmpty(),
]),
jobController.createJobPost
);
// Get All Job Posts
router.get('/', jobController.getAllJobPosts);
// Get Job Post by ID
router.get('/:id', jobController.getJobPostById);
// Update Job Post
router.put(
'/:id',
authMiddleware,
validate([
check('company', 'Company is required').not().isEmpty(),
check('jobType', 'Job type is required').not().isEmpty(),
check('jobDescription', 'Job description is required').not().isEmpty(),
check('jobLocation', 'Job location is required').not().isEmpty(),
]),
jobController.updateJobPost
);
// Delete Job Post
router.delete('/:id', authMiddleware, jobController.deleteJobPost);
module.exports = router;
Update routes/applicationRoutes.js
:
const express = require('express');
const { check } = require('express-validator');
const router = express.Router();
const applicationController = require('../controllers/applicationController');
const authMiddleware = require('../middlewares/authMiddleware');
const validate = require('../middlewares/validationMiddleware');
// Apply for Job
router.post(
'/',
authMiddleware,
validate([check('jobPostId', 'Job post ID is required').not().isEmpty()]),
applicationController.applyForJob
);
// Get Applications for a Job Post
router.get('/job/:jobPostId', authMiddleware, applicationController.getApplicationsForJob);
// Get Applications for a User
router.get('/user', authMiddleware, applicationController.getUserApplications);
// Update Application Status
router.put(
'/:id',
authMiddleware,
validate([check('status', 'Status is required').not().isEmpty()]),
applicationController.updateApplicationStatus
);
module.exports = router;
Step 3: Enhance Notification System with Error Handling and Validation
Update
controllers/notificationController.js
to Handle Errors Gracefully:
const sendEmail = require('../utils/email');
const sendSMS = require('../utils/sms');
// Send Notification
exports.sendNotification = async (req, res) => {
const { email, phone, subject, message } = req.body;
try {
if (email) {
sendEmail(email, subject, message);
}
if (phone) {
sendSMS(phone, message);
}
res.json({ msg: 'Notification sent' });
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
};
2. Update routes/notificationRoutes.js
with Validation:
const express = require('express');
const { check } = require('express-validator');
const router = express.Router();
const notificationController = require('../controllers/notificationController');
const authMiddleware = require('../middlewares/authMiddleware');
const validate = require('../middlewares/validationMiddleware');
// Send Notification
router.post(
'/send',
authMiddleware,
validate([
check('subject', 'Subject is required').not().isEmpty(),
check('message', 'Message is required').not().isEmpty(),
]),
notificationController.sendNotification
);
module.exports = router;
Module 7 implements robust error handling and input validation across the application. By centralizing error handling and ensuring all incoming data is properly validated, we enhance the overall security and reliability of the job portal. These improvements make the application more resilient to errors and ensure that it handles user input in a safe and consistent manner. With these enhancements, the job portal is now better equipped to provide a seamless user experience while maintaining high standards of data integrity and security.
Module 8: Testing and Documentation
In this module, we will focus on setting up testing and documentation for the job portal. This includes writing unit and integration tests to ensure the reliability and correctness of the application and documenting the API endpoints to assist developers in understanding and using the API effectively.
Step 1: Setting Up Testing
Install Testing Libraries:
npm install mocha chai supertest --save-dev
2. Create Test Directory and Files: Create a test
directory in the root of your project. Inside this directory, create separate test files for different functionalities.
job-portal/
├── test/
│ ├── auth.test.js
│ ├── profile.test.js
│ ├── job.test.js
│ ├── application.test.js
│ ├── dashboard.test.js
│ └── report.test.js
Writing Unit and Integration Tests: Example Test for Authentication (test/auth.test.js
):
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../server');
const should = chai.should();
chai.use(chaiHttp);
describe('Auth', () => {
describe('/POST register', () => {
it('it should register a new user', (done) => {
const user = {
email: 'testuser@example.com',
password: 'password123',
userType: 'jobseeker',
};
chai.request(server)
.post('/api/auth/register')
.send(user)
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('token');
done();
});
});
});
describe('/POST login', () => {
it('it should login a user', (done) => {
const user = {
email: 'testuser@example.com',
password: 'password123',
};
chai.request(server)
.post('/api/auth/login')
.send(user)
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('token');
done();
});
});
});
});
Example Test for Profile (test/profile.test.js
):
const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../server');
const should = chai.should();
const User = require('../models/UserAccount');
chai.use(chaiHttp);
let token;
before((done) => {
const user = {
email: 'testuser2@example.com',
password: 'password123',
userType: 'jobseeker',
};
chai.request(server)
.post('/api/auth/register')
.send(user)
.end((err, res) => {
token = res.body.token;
done();
});
});
describe('Profile', () => {
describe('/GET profile', () => {
it('it should GET the user profile', (done) => {
chai.request(server)
.get('/api/profile')
.set('x-auth-token', token)
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('firstName');
res.body.should.have.property('lastName');
done();
});
});
});
describe('/POST profile', () => {
it('it should UPDATE the user profile', (done) => {
const profile = {
firstName: 'John',
lastName: 'Doe',
email: 'testuser2@example.com',
};
chai.request(server)
.post('/api/profile')
.set('x-auth-token', token)
.send(profile)
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('object');
res.body.should.have.property('firstName').eql('John');
res.body.should.have.property('lastName').eql('Doe');
done();
});
});
});
});
4. Update package.json
Scripts: Add a test script to package.json
:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "mocha --timeout 10000 --exit"
}
5.Run the Tests: To run the tests, use the following command:
npm test
Step 2: Documenting the API
Install Swagger:
npm install swagger-jsdoc swagger-ui-express
Create Swagger Configuration: Create config/swagger.js
:
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Job Portal API',
version: '1.0.0',
description: 'API documentation for the Job Portal',
},
servers: [
{
url: 'http://localhost:5000',
},
],
},
apis: ['./routes/*.js'],
};
const specs = swaggerJsdoc(options);
module.exports = {
swaggerUi,
specs,
};
Update Routes with Swagger Annotations: Add Swagger annotations to your route files.
Example for routes/authRoutes.js
:
/**
* @swagger
* /api/auth/register:
* post:
* summary: Register a new user
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* - userType
* properties:
* email:
* type: string
* password:
* type: string
* userType:
* type: string
* responses:
* 200:
* description: User registered successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* token:
* type: string
* 400:
* description: Bad request
* 500:
* description: Server error
*/
router.post(
'/register',
validate([
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
]),
authController.registerUser
);
Serve Swagger Documentation: Update server.js
to Serve Swagger Docs:
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const passport = require('passport');
const errorHandler = require('./middlewares/errorMiddleware');
const { swaggerUi, specs } = require('./config/swagger');
require('./config/linkedin');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
app.use(passport.initialize());
// Swagger Documentation
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.use('/api/dashboard', require('./routes/dashboardRoutes'));
app.use('/api/reports', require('./routes/reportRoutes'));
app.use('/api/resume', require('./routes/resumeRoutes'));
app.use('/api/notifications', require('./routes/notificationRoutes'));
// LinkedIn Authentication Routes
app.get('/api/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
app.get(
'/api/auth/linkedin/callback',
passport.authenticate('linkedin', {
successRedirect: '/',
failureRedirect: '/login',
})
);
app.get('/', (req, res) => {
res.send('Job Portal API');
});
// Error Handling Middleware
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
5.
Access API Documentation: Once the server is running, you can access the API documentation at
http://localhost:5000/api-docs
.
Module 8 introduces comprehensive testing and documentation for the job portal. By writing unit and integration tests, you ensure the reliability and correctness of the application. Additionally, by documenting the API with Swagger, you provide developers with a clear and accessible reference for using the API. These practices enhance the overall quality and maintainability of the job portal, making it easier to extend and improve in the future.
Module 9: Deployment and Maintenance
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
In this final module, we will focus on deploying the job portal to a cloud platform and setting up continuous integration and deployment (CI/CD) pipelines. We will also discuss maintenance practices to ensure the application remains secure, efficient, and up-to-date.
Step 1: Deployment
Choose a Cloud Platform: We will deploy the job portal to Heroku for simplicity, but you can choose other cloud platforms like AWS, Google Cloud, or DigitalOcean.
Prepare for Deployment:
- Ensure all environment variables are configured in a
.env
file. - Modify
server.js
to use the environment variable for the port.
Update
server.js
:- Ensure all environment variables are configured in a
const express = require('express');
const connectDB = require('./config/db');
const dotenv = require('dotenv');
const passport = require('passport');
const errorHandler = require('./middlewares/errorMiddleware');
const { swaggerUi, specs } = require('./config/swagger');
require('./config/linkedin');
dotenv.config();
const app = express();
// Connect to MongoDB
connectDB();
// Middleware
app.use(express.json());
app.use(passport.initialize());
// Swagger Documentation
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// Routes
app.use('/api/auth', require('./routes/authRoutes'));
app.use('/api/profile', require('./routes/profileRoutes'));
app.use('/api/jobs', require('./routes/jobRoutes'));
app.use('/api/applications', require('./routes/applicationRoutes'));
app.use('/api/dashboard', require('./routes/dashboardRoutes'));
app.use('/api/reports', require('./routes/reportRoutes'));
app.use('/api/resume', require('./routes/resumeRoutes'));
app.use('/api/notifications', require('./routes/notificationRoutes'));
// LinkedIn Authentication Routes
app.get('/api/auth/linkedin', passport.authenticate('linkedin', { state: 'SOME STATE' }));
app.get(
'/api/auth/linkedin/callback',
passport.authenticate('linkedin', {
successRedirect: '/',
failureRedirect: '/login',
})
);
app.get('/', (req, res) => {
res.send('Job Portal API');
});
// Error Handling Middleware
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3.Deploy to Heroku:
- Install the Heroku CLI: Heroku CLI
- Log in to Heroku
heroku login
Create a new Heroku application
heroku create job-portal
Add MongoDB Add-on (Optional, if not using an external MongoDB service)
heroku addons:create mongolab
Push your code to Heroku
git push heroku master
Set up environment variables on Heroku
heroku config:set MONGO_URI=your_mongodb_connection_string
heroku config:set JWT_SECRET=your_jwt_secret
heroku config:set LINKEDIN_CLIENT_ID=your_linkedin_client_id
heroku config:set LINKEDIN_CLIENT_SECRET=your_linkedin_client_secret
heroku config:set EMAIL_USER=your_email_user
heroku config:set EMAIL_PASS=your_email_password
heroku config:set TWILIO_ACCOUNT_SID=your_twilio_account_sid
heroku config:set TWILIO_AUTH_TOKEN=your_twilio_auth_token
heroku config:set TWILIO_PHONE_NUMBER=your_twilio_phone_number
heroku config:set BASE_URL=https://your-app.herokuapp.com
4.Access Your Application:
- Open your application in the browser
heroku open
Step 2: Continuous Integration and Deployment (CI/CD)
Set Up GitHub Actions for CI/CD:
- Create a
.github/workflows
directory in your project root. - Inside this directory, create a
ci-cd.yml
file.
Example
ci-cd.yml
:- Create a
name: CI/CD Pipeline
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Deploy to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
git remote add heroku https://git.heroku.com/your-app.git
git push heroku master
- Add the Heroku API key to your GitHub secrets:
- Go to your repository on GitHub.
- Click on “Settings” -> “Secrets” -> “New repository secret”.
- Add
HEROKU_API_KEY
with your Heroku API key.
- Add the Heroku API key to your GitHub secrets:
Test the CI/CD Pipeline:
- Make a change to your code and push it to the
master
branch to trigger the CI/CD pipeline.
- Make a change to your code and push it to the
Step 3: Maintenance
Monitoring and Logging:
- Use Heroku’s built-in logging to monitor your application.
- Consider integrating with third-party monitoring tools like New Relic or Sentry for more advanced monitoring.
Regular Updates:
- Regularly update your dependencies to patch security vulnerabilities.
- Monitor your application’s performance and optimize as necessary.
Security:
- Regularly review and update your security settings.
- Ensure that sensitive data such as API keys and database credentials are stored securely.
Backup and Recovery:
- Implement a backup strategy for your MongoDB database.
- Test your backup and recovery procedures regularly to ensure data integrity.
Module 9 completes the development of the job portal by focusing on deployment and maintenance. By deploying the application to a cloud platform like Heroku and setting up a CI/CD pipeline with GitHub Actions, you ensure that your application is always up-to-date and running smoothly. Implementing monitoring, logging, and regular updates, along with a strong focus on security and backup strategies, will help maintain the application’s reliability and performance over time. With these steps, the job portal is now ready for production and ongoing maintenance, providing a robust platform for job seekers and recruiters.

Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.