Open In App

Job Search Portal with MERN Stack

Last Updated : 25 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this tutorial, we’ll guide you through the process of creating a Job Search Portal using the MERN Stack – MongoDB for storing job and user data, Express.js for building web applications, React.js for creating user interfaces, and Node.js for server-side programming.

Output Preview:

Screenshot-_223_

Preview

Prerequisite

Approach

  • Begin by creating a server.js file using Node.js and Express.js to handle routing and API calls.
  • Integrate MongoDB for storing job data, utilizing Mongoose library for interaction.
  • Define a schema/model for job listings in MongoDB, specifying fields like job title, company, location, etc., for consistency.
  • Create API endpoints for job listing interactions, which will be accessed by the frontend (built with React.js) for fetching and manipulating data.
  • Once the backend is established, integrate it with the frontend using React.js to design the user interface for the Job Search Portal. Develop components for displaying listings, implementing search functionality, applying filters, and interacting with the backend API.

Steps to Setup backend for project

Step 1: Make a folder in the root directory using the following command

mkdir backend

Step 2: Once it is done move to your newly created directory.

cd backend

Step 3: Installing the required packages

npm i express mongoose  cors axios

Step 4: Create a file that is server.js

touch server.js

Project Structure :

Screenshot-2024-04-15-155507

The updated dependencies in package.json file will look like:

"dependencies": {
    "axios": "^1.6.7",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "mongoose": "^8.2.0"
  }

Example: Implementation to setup the backend for the above the project.

JavaScript
// server.js

const express = require('express');
const cors = require('cors');
const app = express();
const connectDB = require('./db');
const Userdb = require('./models');
const port = 3001;

// Connect to the database
connectDB();

// Middleware to parse JSON bodies
app.use(express.json());

// Enable CORS
app.use(cors());

// Home route
app.get("/", (req, res) => {
    res.send("<h1>Home page of a Job portal </h1>")
});

// Create a new job
app.post("/api/jobs", async (req, res) => {
    const { title,
        company,
        location,
        description, salary } = req.body;
    try {
        const newJob = new Userdb({
            title,
            company,
            location,
            description,
            salary
        });
        await newJob.save();
        res.status(201).json(newJob);
    } catch (error) {
        console.error('Error creating job:', error);
        res.status(500).json(
            { message: 'Server Error' }
        );
    }
});

// Get all jobs
app.get("/api/jobs", async (req, res) => {
    try {
        const jobs = await Userdb.find();
        res.json(jobs);
    } catch (error) {
        console.error('Error getting jobs:', error);
        res.status(500).json(
            { message: 'Server Error' }
        );
    }
});

// Start the server
app.listen(port, () => {
    console.log(
        `Server is started at port no ${port}`);
});
JavaScript
// db.js 

const mongoose = require('mongoose');

const connectDB = async () => {
    try {
        await mongoose.connect('YOURS URI', {

        });
        console.log('Database is connected');
    } catch (err) {
        console.error('Error connecting to the database:', err);
        process.exit(1);
    }
};

module.exports = connectDB;
JavaScript
//model.js

const mongoose = require('mongoose');

const schema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    company: {
        type: String,
        required: true
    },
    location: String,
    description: String,
    salary: {
        type: Number,
        required: true
    }
});

const Userdb = mongoose.model('userdb', schema);

module.exports = Userdb;

Steps to run the backend Application

node server.js

Steps to Setup Frontend for project

Step 1: Create a reactJS application by using this command

npx create-react-app client

Step 2: Navigate to project directory

cd client

Step 3: Install the necessary packages/libraries in your project using the following commands.

npm install react-router-dom
npm install react-toastify

Project Structure:

Screenshot-2024-04-15-160728

The updated dependencies in package.json file will look like:

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.6.7",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.1",
    "react-scripts": "5.0.1",
    "react-toastify": "^10.0.4",
    "web-vitals": "^2.1.4"
  }
}

Example : Implementation to setup the frontend for the above the project.

CSS
/* App.css */

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    line-height: 1.6;
    color: #333;
}

header {
    background-color: #17dd34;
    color: #fff;
    text-align: center;
    padding: 1rem;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
}

nav {
    position: fixed;
    top: 0;
    right: 0;
}

nav ul {
    list-style: none;
    display: flex;
}

nav li {
    margin-right: 20px;
    margin-top: 30px;
}

nav a {
    text-decoration: none;
    color: #fff;
    font-weight: bold;
    transition: color 0.3s;
    font-size: 20px;
}

nav a:hover {
    color: #ff6600;
}


.search {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    margin-top: 5rem;
    margin-right: 35rem;
}

.search h2 {
    font-size: 24px;
    margin-bottom: 1rem;
    text-align: right;
    color: blue;
}



.job-listings {
    margin: 2rem 20%;
    position: relative;
}





.job-listings h2 {
    font-size: 24px;
    margin-bottom: 1rem;
    text-align: center;
}

.job-listings ul {
    list-style: none;
}

.job-listings li {
    background-color: #fff;
    border: 1px solid #ccc;
    padding: 20px;
    margin-bottom: 20px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.job-listings h3 {
    font-size: 20px;
    margin-bottom: 10px;
}

.job-listings p {
    font-size: 14px;
    margin-bottom: 10px;
}

.job-listings a {
    background-color: #333;
    color: #fff;
    text-decoration: none;
    padding: 10px 20px;
    border-radius: 4px;
    display: inline-block;
    transition: background-color 0.3s;
}

.job-listings a:hover {
    background-color: #ff6600;
}

footer {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 50px;
    /* Adjust height as needed */
    text-align: center;
    background-color: #333;
    color: #fff;
    padding: 1rem 0;
}

.App {
    min-height: 100vh;
    position: relative;
}

.job-container {
    background-color: #fff;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    padding: 20px;
    margin-bottom: 20px;
}

.job-container h3 {
    color: #333;
    font-size: 20px;
    margin-bottom: 10px;
}

.job-container p {
    color: #666;
    margin-bottom: 8px;
}

.job-container .new-job {
    background-color: #f0f8ff;
    /* Light blue */
}


.No_job {
    color: #ff3300;
    /* font-size: 50px;  */
    display: flex;
    justify-content: center;

}

.No_job_avilable {
    color: #ff3300;
    display: flex;
    justify-content: center;
}

.apply_button {
    background-color: #46812a;
    color: #fff;
    border: none;
    padding: 10px 20px;
    border-radius: 3px;
    cursor: pointer;
    transition: background-color 0.3s;
}

.apply_button:hover {
    background-color: #fff;
    color: #1556d8;
    border: none;
    padding: 10px 20px;
    border-radius: 3px;
    cursor: pointer;
    transition: background-color 0.3s;
    border: 2px solid #1556d8;
}

.brand_name {
    display: flex;
    justify-content: flex-start;

}

.search_button {
    width: 20vw;
    height: 40px;
    border: 2px solid #ccc;
    border-radius: 5px;
    padding: 5px 10px;
    font-size: 16px;
    outline: none;
    background-repeat: no-repeat;

}

.search_button:focus {
    border-color: #007bff;
}

.company {
    color: green;
}


.sticky-header {
    position: sticky;
    top: 0;
    background-color: #17dd34;
    color: #fff;
    text-align: center;
    padding: 1rem;
    z-index: 1000;
}
CSS
/* PostJob.css */

form {
    max-width: 500px;
    margin: 0 auto;
    padding: 30px;
    border: 1px solid hsl(0, 0%, 80%);
    border-radius: 5px;
    background-color: #f9f9f9;
    margin-top: 100px;
}

form div {
    margin-bottom: 15px;
}

form label {
    display: block;
    margin-bottom: 5px;
}

form input[type="text"],
form input[type="number"],
form textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 3px;
}

form textarea {
    height: 100px;
}

form button[type="submit"] {
    background-color: #46812a;
    color: #fff;
    border: none;
    padding: 10px 20px;
    border-radius: 3px;
    cursor: pointer;
    transition: background-color 0.3s;
}

form button[type="submit"]:hover {
    background-color: #fff;
    color: #3a6e1d;
    border: 2px solid #3a6e1d;
}
JavaScript
// App.js

import React from 'react';
import './App.css';
import {
  BrowserRouter as Router,
  Routes, Route,
  Link
} from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import Home from './Home';
import PostJob from './PostJob';

function App() {
  return (
    <Router>
      <div>
        <ToastContainer />
        <Routes>
          <Route path="/postjob" element={<PostJob />} />
          <Route path="/" element={<Home />} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;
JavaScript
// Home.js

import React, { useState, useEffect } from "react";
import './App.css';
import axios from 'axios';

const Home = () => {
    const [jobs, setJobs] = useState([]);
    const [searchQuery, setSearchQuery] = useState('');
    const [filteredJobs, setFilteredJobs] = useState([]);
    const [noMatch, setNoMatch] = useState(false);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchJobs();
    }, []);

    const fetchJobs = async () => {
        try {
            const response =
                await axios.get('http://localhost:3001/api/jobs');
            setJobs(response.data);
            setFilteredJobs(response.data);
            setLoading(false);
        } catch (error) {
            console.error('Error fetching jobs:', error);
            setLoading(false);
        }
    };

    const handleInputChange = (e) => {
        setSearchQuery(e.target.value);
        filterJobs(e.target.value);
    };

    const filterJobs = (keyword) => {
        if (!keyword) {
            setFilteredJobs(jobs);
            setNoMatch(false);
            return;
        }
        const filtered = jobs.filter(job =>
            job.title.toLowerCase().includes(keyword.toLowerCase()) ||
            job.company.toLowerCase().includes(keyword.toLowerCase()) ||
            job.location.toLowerCase().includes(keyword.toLowerCase())
        );
        setFilteredJobs(filtered);
        setNoMatch(filtered.length === 0);
    };

    return (
        <div className="App">
            <header className="sticky-header">
                <h1 className="brand_name">
                    Geeksforgeeks Job
                    Search Portal
                </h1>
                <nav>
                    <ul>
                        <li><a href="#">Home</a></li>
                        <li><a href="/postjob">Post_Job</a></li>
                        <li><input
                            type="text"
                            placeholder="Search your dream company..."
                            value={searchQuery}
                            onChange={handleInputChange}
                            className="search_button"
                        /></li>
                    </ul>
                </nav>
            </header>

            <div className="content-container">
                <section className="job-listings">
                    <h2>Latest Job Listings</h2>
                    {loading ? (
                        <h2>Loading...</h2>
                    ) : jobs.length === 0 ? (
                        <h2 className="No_job_avilable">
                            No jobs available
                        </h2>
                    ) : noMatch ? (
                        <h2 className="No_job">
                            No matching job found
                        </h2>
                    ) : (
                        filteredJobs.map(job => (
                            <div key={job._id}
                                className={`job-container 
                                    ${job.isNew ? 'new-job' : ''}`}>
                                <h3 className="job_title">
                                    {job.title}
                                </h3>
                                <p className="company">
                                    Company: {job.company}
                                </p>
                                <p className="loaction">
                                    Location: {job.location}
                                </p>
                                <p>Description:{job.description}</p>
                                <p className="salary">
                                    Salary: {job.salary}
                                </p>
                                <button className="apply_button">
                                    Apply
                                </button>
                            </div>
                        ))
                    )}
                </section>
            </div>
        </div>
    );
};

export default Home;
JavaScript
// PostJob.js 

import React, { useState } from 'react';
import axios from 'axios';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import './PostJob.css';

const PostJob = () => {
    const [title, setTitle] = useState("");
    const [company, setCompany] = useState("");
    const [location, setLocation] = useState("");
    const [description, setDescription] = useState("");
    const [salary, setSalary] = useState("");
    const [jobs, setJobs] = useState([]);

    const handleOnSubmit = async (e) => {
        e.preventDefault();
        
        // Check if any of the fields are empty
        if (!title || !company ||
            !location || !description
            || !salary) {
            toast.error('Please filled all fields to post a job.');
            return;
        }
        try {
            const response = await axios.post(
                'http://localhost:3001/api/jobs',
                { title, company, location, description, salary }
            );
            if (response.status === 201) {
                toast.success('Job added successfully!');
                setTitle("");
                setCompany("");
                setLocation("");
                setDescription("");
                setSalary("");

                // Fetch the updated list of jobs
                axios.get('http://localhost:3001/api/jobs')
                    .then(response => setJobs(response.data))
                    .catch(err => console.log(err));
            }
        } catch (error) {
            console.error('Error adding job:', error);
            toast.error('Failed to add job. Please try again.');
        }
    };

    return (
        <div>
            <header>
                <h1>Job Search Portal</h1>
            </header>
            <nav>
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="#">Post_Job</a></li>
                </ul>
            </nav>
            <form onSubmit={handleOnSubmit}>
                <div>
                    <label>Title:</label>
                    <input
                        type="text"
                        placeholder="Job title"
                        value={title}
                        onChange={(e) => setTitle(e.target.value)} />
                </div>
                <div>
                    <label>Company:</label>
                    <input
                        type="text"
                        placeholder="Company Name..."
                        value={company}
                        onChange={
                            (e) => setCompany(e.target.value)} />
                </div>
                <div>
                    <label>Location:</label>
                    <input
                        type="text"
                        placeholder="Company Location..."
                        value={location}
                        onChange={
                            (e) => setLocation(e.target.value)} />
                </div>
                <div>
                    <label>Description:</label>
                    <input
                        type="text"
                        placeholder="Description..."
                        value={description}
                        onChange={
                            (e) => setDescription(e.target.value)} />
                </div>
                <div>
                    <label>Salary:</label>
                    <input
                        type="number"
                        placeholder="salary..."
                        value={salary}
                        onChange={(e) => setSalary(e.target.value)} />
                </div>
                <button type="submit">Submit</button>
            </form>
        </div>
    );
};

export default PostJob;

Step to Run Application: Run the application using the following command from the root directory of the project

npm start

Output: Your project will be shown in the URL http://localhost:3000/

xsw




Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads