Building Instructor Profiles Page in MERN Stack Course Management App

Last Updated : 3 Sep, 2024

Instructor Profiles are a key feature in educational listing platforms, offering a window into the qualifications and expertise of the educators behind each course. These profiles provide potential students with valuable information about the instructors, helping them make informed decisions about their learning journey.

Key Features Of Instructor Profiles

Typically, an Instructor Profile includes the instructor’s name, photo, and a detailed biography that highlights their educational background, professional experience, and areas of expertise. Additional elements such as certifications, awards, and links to published work or social media profiles may also be featured, adding credibility and personal connection.

Instructor Profiles often showcase student reviews and ratings, providing insights into the instructor's teaching style and effectiveness. This feedback loop not only helps students choose the right courses but also motivates instructors to maintain high teaching standards.

In summary, Instructor Profiles are essential in building trust and transparency within educational platforms, offering a personal touch that enhances the overall learning experience.

Approach to implement Instructor Profiles

Backend

  • Router Setup: Use express.Router() to define routes for managing instructors.
  • Route Definitions: Set up routes to handle getting all instructors, creating a new instructor, retrieving an instructor by ID, and deleting an instructor.
  • Controller Functions: Implement functions to interact with the Instructor model, including fetching, creating, retrieving, and deleting instructor data.
  • Error Handling: Handle errors with appropriate status codes and messages to ensure robust error management.
  • Data Management: Use Mongoose to define the schema and interact with the MongoDB database for CRUD operations.
  • Modular Code: Organize routes and controller logic in separate files for maintainability and clarity.

Frontend

  • Course Component: Displays course details, including image, title, description, creator, and stats, with options to watch or add to a playlist.
  • Courses Component: Fetches course data, filters by keyword and category, and renders a list of Course components with search and category filtering.
  • InstructorProfiles Component: Fetches and displays a list of instructors with their profile details, such as name, bio, expertise, and courses.
  • InstructorProfile Component: Fetches and displays details of a single instructor based on the ID from URL parameters, including their bio, expertise, and list of courses.
  • Data Fetching and State Management: Utilizes axios for API calls and React's useState and useEffect for managing and updating component state.

Backend Code

JavaScript
// app.js

const app = require("./app")
const { config } = require("dotenv")
const database = require('./config/database');
const cloudinary = require('cloudinary').v2;

database.connectedToDatabase();

cloudinary.config({
    cloud_name: process.env.CLOUD_NAME,
    api_key: process.env.CLOUD_API_KEY,
    api_secret: process.env.CLOUD_API_SECRET,
});

config({
    path: './config/config.env'
})

app.listen(process.env.PORT, () => {
    console.log(`Server is Running at ${process.env.PORT}`);
})
JavaScript
// server.js

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const user = require('./routes/userRoute')
const course = require('./routes/courseRoutes');
const ErrorMiddleware = require('./middleware/Error');
const cookieParser = require('cookie-parser');
const enrollment = require('./routes/enrollmentRoutes')
const instructorRoutes = require('./routes/instructorRoutes');
const cors = require('cors')


app.use(cors({
    origin: ['http://localhost:3000'],
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
}));

app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser())

app.use('/api/v1', user)
app.use('/api/v1', enrollment)
app.use('/api/v1', course)
app.use('/api/v1', instructorRoutes);
app.use(ErrorMiddleware);


module.exports = app;
JavaScript
// config/database.js

const mongoose = require('mongoose');

const connectedToDatabase = async () => {
    try {
        await mongoose.connect('mongodb://localhost:27017/course', {
            useNewUrlParser: true,
            useUnifiedTopology: true
        });
        console.log('Connected to MongoDB');
    } catch (error) {
        console.error('MongoDB connection error:', error);
    }
}

module.exports = { connectedToDatabase }
JavaScript
// Models/instructorModel.js

const mongoose = require('mongoose');

const InstructorSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
    },
    bio: {
        type: String,
        required: true,
    },
    expertise: {
        type: String,
        required: true,
    },
    imageSrc: {
        type: String,
        required: true,
    },
    courses: {
        type: [String],
        required: true,
    },
});

const Instructor = mongoose.model('Instructor', InstructorSchema);

module.exports = Instructor;
JavaScript
// controllers/instructorController.js

const Instructor = require('../models/InstructorModel');

// Get all instructors
exports.getAllInstructors = async (req, res) => {
    try {
        const instructors = await Instructor.find();
        res.status(200).json(instructors);
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};

// Create a new instructor
exports.createInstructor = async (req, res) => {
    const { name, bio, expertise, imageSrc, courses } = req.body;

    try {
        const newInstructor = new Instructor({
            name,
            bio,
            expertise,
            imageSrc,
            courses,
        });

        const instructor = await newInstructor.save();
        res.status(201).json(instructor);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
};

// Get instructor by ID
exports.getInstructorById = async (req, res) => {
    try {
        const instructor = await Instructor.findById(req.params.id);
        if (!instructor) {
            return res.status(404).json({ message: 'Instructor not found' });
        }
        res.status(200).json(instructor);
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};

// Delete an instructor
exports.deleteInstructor = async (req, res) => {
    try {
        const instructor = await Instructor.findById(req.params.id);
        if (!instructor) {
            return res.status(404).json({ message: 'Instructor not found' });
        }

        await instructor.remove();
        res.status(200).json({ message: 'Instructor removed' });
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};
JavaScript
// routes/instructorRoutes.js

const express = require('express');
const router = express.Router();
const enrollmentController = require('../controllers/enrollmentController');

// Create a new enrollment
router.post('/enrollments', enrollmentController.createEnrollment);

// Get all enrollments
router.get('/enrollments', enrollmentController.getAllEnrollments);

// Get a specific enrollment by ID
router.get('/enrollments/:id', enrollmentController.getEnrollmentById);

// Update an enrollment by ID
router.put('/enrollments/:id', enrollmentController.updateEnrollment);

// Delete an enrollment by ID
router.delete('/enrollments/:id', enrollmentController.deleteEnrollment);

module.exports = router;


Start your server using the below command:

node index.js

Frontend Code

JavaScript
// App.jsx

import React from "react";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import Home from "./components/Home/Home";
import Header from "../src/components/Layout/Header/Header";
import Courses from "./components/Courses/Courses";
import Footer from "./components/Layout/Footer/Footer";
import Login from "./components/Auth/Login";
import Register from "./components/Auth/Register";
import ForgotPassword from "./components/Auth/ForgotPassword";
import ResetPassword from "./components/Auth/ResetPassword";
import Contact from "./components/Contact/Contact";
import Request from "./components/Request/Request";
import About from "./components/About/About";
import Subscribe from "./components/Payments/Subscribe";
import CoursePage from "./components/CoursePage/CoursePage";
import Profile from "./components/Profile/Profile";
import ChangePassword from "./components/Profile/ChangePassword";
import UpdateProfile from "./components/Profile/UpdateProfile";
import Dashboard from "./components/Admin/Dashboard/Dashboard";
import CreateCourses from "./components/Admin/CreateCourse/CreateCourses";
import AdminCourses from "./components/Admin/AdminCourses/AdminCourses";
import Users from "./components/Admin/Users/Users";
import EnrollmentForm from "./components/Auth/EnrollmentForm";
import InstructorProfiles from "./components/Instructor/Instructor";
import InstructorProfile from "./components/Instructor/InstructorProfile";

function App() {
    return (
        <Router>
            <Header />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/courses" element={<Courses />} />
                <Route path="/course/:id" element={<CoursePage />} />
                <Route path="/contact" element={<Contact />} />
                <Route path="/request" element={<Request />} />
                <Route path="/about" element={<About />} />
                <Route path="/profile" element={<Profile />} />
                <Route path="/changepassword" element={<ChangePassword />} />
                <Route path="/updateprofile" element={<UpdateProfile />} />
                <Route path="/login" element={<Login />} />
                <Route path="/signup" element={<Register />} />
                <Route path="/forgetpassword" element={<ForgotPassword />} />
                <Route path="/resetpassword/:token" element={<ResetPassword />} />
                <Route path="/subscribe" element={<Subscribe />} />
                <Route path="/admin/dashboard" element={<Dashboard />} />
                <Route path="/admin/createcourse" element={<CreateCourses />} />
                <Route path="/admin/courses" element={<AdminCourses />} />
                <Route path="/admin/users" element={<Users />} />
                <Route path="/enroll" element={<EnrollmentForm />} />
                <Route path="/inst" element={<InstructorProfiles />} />
                <Route path="/instructor/:id" element={<InstructorProfile />} />
            </Routes>
            <Footer />
        </Router>
    );
}

export default App;
JavaScript
// src/components/Instructor/InstructorProfiles.jsx

import React, { useEffect, useState } from "react";
import axios from "axios";
import { Box, Avatar, Text, Stack, Container, Heading } from "@chakra-ui/react";

const InstructorProfiles = () => {
    const [instructors, setInstructors] = useState([]);

    useEffect(() => {
        const fetchInstructors = async () => {
            try {
                const { data } = await axios.get(
                    "http://localhost:4000/api/v1/instructors"
                );
                setInstructors(data);
            } catch (error) {
                console.error("Error fetching instructors:", error);
            }
        };

        fetchInstructors();
    }, []);

    return (
        <Container maxW="container.xl" py={8}>
            <Heading mb={6}>Instructor Profiles</Heading>
            <Stack spacing={6}>
                {instructors.map((instructor) => (
                    <Box
                        key={instructor._id}
                        p={5}
                        shadow="md"
                        borderWidth="1px"
                        borderRadius="md"
                        bg="white"
                    >
                        <Stack direction="row" spacing={4} align="center">
                            <Avatar name={instructor.name} src={instructor.imageSrc} />
                            <Stack spacing={1}>
                                <Text fontWeight="bold" color={"black"} fontSize="xl">
                                    {instructor.name}
                                </Text>
                                <Text color="gray.600">{instructor.bio}</Text>
                                <Text color="teal.500">{instructor.expertise}</Text>
                                <Text color="blue.500">
                                    Courses: {instructor.courses.join(", ")}
                                </Text>
                            </Stack>
                        </Stack>
                    </Box>
                ))}
            </Stack>
        </Container>
    );
};

export default InstructorProfiles;
JavaScript
// src/components/Instructor/instructorProfile.jsx

import React, { useState, useEffect } from "react";
import {
    Avatar,
    Box,
    Container,
    Heading,
    Stack,
    Text,
    useToast,
    Divider,
    VStack,
    Button,
} from "@chakra-ui/react";
import axios from "axios";
import { useParams } from "react-router-dom";

const InstructorProfile = () => {
    const { id } = useParams();
    const [instructor, setInstructor] = useState(null);
    const toast = useToast();

    useEffect(() => {
        const fetchInstructor = async () => {
            try {
                const { data } = await axios.get(
                    `http://localhost:4000/api/v1/instructor/${id}`
                );
                setInstructor(data);
            } catch (error) {
                toast({
                    title: "Error fetching instructor.",
                    description:
                        error.response?.data?.message ||
                        "An error occurred while fetching instructor details.",
                    status: "error",
                    duration: 5000,
                    isClosable: true,
                });
            }
        };

        fetchInstructor();
    }, [id, toast]);

    if (!instructor) {
        return <Text>Loading...</Text>;
    }

    return (
        <Container maxW="container.md" py={16}>
            <VStack spacing={6} align="center">
                <Box
                    borderWidth={1}
                    borderRadius="lg"
                    overflow="hidden"
                    boxShadow="md"
                    p={4}
                    bg="white"
                    w="full"
                >
                    <Avatar
                        name={instructor.name}
                        src={instructor.imageSrc}
                        size="xl"
                        mb={4}
                    />
                    <Heading as="h1" size="lg" mb={2} textAlign="center">
                        {instructor.name}
                    </Heading>
                    <Text fontSize="md" color="gray.600" textAlign="center">
                        {instructor.bio}
                    </Text>
                    <Divider my={4} />
                    <Stack spacing={3} align="center">
                        <Text fontSize="md" fontWeight="bold" color="teal.500">
                            Expertise
                        </Text>
                        <Text fontSize="md" textAlign="center">
                            {instructor.expertise}
                        </Text>
                        <Divider />
                        <Text fontSize="md" fontWeight="bold" color="teal.500">
                            Courses
                        </Text>
                        <Text fontSize="md" textAlign="center">
                            {instructor.courses}
                        </Text>
                    </Stack>
                </Box>
                <Button
                    colorScheme="teal"
                    variant="solid"
                    size="lg"
                    mt={6}
                    onClick={() => alert("Follow Instructor")}
                >
                    Follow Instructor
                </Button>
            </VStack>
        </Container>
    );
};

export default InstructorProfile;
JavaScript
// src/components/Courses/Course.jsx

import {
    Button,
    Container,
    HStack,
    Heading,
    Image,
    Input,
    Stack,
    Text,
    VStack,
} from "@chakra-ui/react";
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";

const Course = ({
    views,
    title,
    imageSrc,
    id,
    addToPlaylistHandler,
    creator,
    description,
    lectureCount,
}) => {
    return (
        <VStack className="course" alignItems={["center", "flex-start"]}>
            <Image src={imageSrc} boxSize={"60"} objectFit={"contain"} />
            <Heading
                textAlign={["center", "left"]}
                maxW={"200px"}
                size={"sm"}
                fontFamily={"sans-serif"}
                noOfLines={3}
                children={title}
            />
            <Text children={description} noOfLines={2} />
            <HStack>
                <Text
                    fontWeight={"bold"}
                    textTransform={"uppercase"}
                    children={"Creator :"}
                />
                <Link to={`/instructor/${creator?._id}`}>
                    <Text
                        fontFamily={"body"}
                        textTransform={"uppercase"}
                        color="blue.500"
                        _hover={{ textDecoration: "underline" }}
                    >
                        {creator?.name || "Unknown Creator"}
                    </Text>
                </Link>
            </HStack>
            <Heading
                textAlign={"center"}
                size={"xs"}
                children={`Lectures - ${lectureCount}`}
                textTransform={"uppercase"}
            />
            <Heading
                size={"xs"}
                children={`Views - ${views}`}
                textTransform={"uppercase"}
            />
            <Stack direction={["column", "row"]} alignItems={"center"}>
                <Link to={`/course/${id}`}>
                    <Button colorScheme="yellow">Watch Now</Button>
                </Link>
                <Button
                    variant={"ghost"}
                    colorScheme="yellow"
                    onClick={() => addToPlaylistHandler(id)}
                >
                    Add To Playlist
                </Button>
            </Stack>
        </VStack>
    );
};

const Courses = () => {
    const [courses, setCourses] = useState([]);
    const [keyword, setKeyword] = useState("");
    const [category, setCategory] = useState("");

    const categories = [
        "App Development",
        "Web Development",
        "Data Structures & Algorithm",
        "Machine Learning",
        "Artificial Intelligence",
        "Data Science",
    ];

    useEffect(() => {
        const fetchCourses = async () => {
            try {
                const { data } = await axios.get(
                    "http://localhost:4000/api/v1/course",
                    {
                        withCredentials: true,
                    }
                );
                setCourses(data.courses);
            } catch (error) {
                console.error(error.response.data.message);
            }
        };

        fetchCourses();
    }, []);

    // Filter courses based on keyword and category
    const filteredCourses = courses.filter(
        (course) =>
            course.title.toLowerCase().includes(keyword.toLowerCase()) &&
            (category ? course.category === category : true)
    );

    const addToPlaylistHandler = (courseId) => {
        console.log("Add To playlist", courseId);
    };

    return (
        <Container minH={"95vh"} maxW={"container.lg"} paddingY={"8"}>
            <Heading children="All Courses" m={"8"} />

            <Input
                value={keyword}
                onChange={(e) => setKeyword(e.target.value)}
                placeholder="Search a course"
                type="text"
                focusBorderColor="yellow"
            />

            <HStack
                overflowX={"auto"}
                padding={"8"}
                css={{ "&::-webkit-scrollbar": { display: "none" } }}
            >
                {categories.map((item, index) => (
                    <Button
                        colorScheme="yellow"
                        variant={"ghost"}
                        key={index}
                        onClick={() => setCategory(item)}
                        minW={"60"}
                    >
                        <Text children={item} />
                    </Button>
                ))}
                <Button
                    colorScheme="yellow"
                    variant={"ghost"}
                    onClick={() => setCategory("")}
                    minW={"60"}
                >
                    <Text children="All Categories" />
                </Button>
            </HStack>

            <Stack
                direction={["column", "row"]}
                flexWrap={"wrap"}
                justifyContent={["flex-start", "space-evenly"]}
                alignItems={["center", "flex-start"]}
            >
                {filteredCourses.length > 0 ? (
                    filteredCourses.map((course) => (
                        <Course
                            key={course._id}
                            title={course.title}
                            description={course.description}
                            views={course.views}
                            imageSrc={course.poster.url}
                            id={course._id}
                            creator={course.createdBy}
                            lectureCount={course.numOfVideos}
                            addToPlaylistHandler={addToPlaylistHandler}
                        />
                    ))
                ) : (
                    <Text>No courses found</Text>
                )}
            </Stack>
        </Container>
    );
};

export default Courses;
JavaScript
// src/components/Admin/CreateCourse/CreateCourse.jsx

import {
    Button,
    Container,
    Grid,
    Heading,
    Image,
    Input,
    Select,
    VStack,
} from "@chakra-ui/react";
import React, { useEffect, useState } from "react";
import axios from "axios";
import Sidebar from "../Sidebar";

const CreateCourses = () => {
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [createdBy, setCreatedBy] = useState("");
    const [category, setCategory] = useState("");
    const [image, setImage] = useState(null);
    const [imagePrev, setImagePrev] = useState("");
    const [instructors, setInstructors] = useState([]);

    const categories = [
        "App Development",
        "Web Development",
        "Data Structures & Algorithm",
        "Machine Learning",
        "Artificial Intelligence",
        "Data Science",
    ];

    useEffect(() => {
        const fetchInstructors = async () => {
            try {
                const { data } = await axios.get(
                    "http://localhost:4000/api/v1/instructors"
                );
                setInstructors(data);
            } catch (error) {
                console.error("Error fetching instructors:", error);
            }
        };

        fetchInstructors();
    }, []);

    const changeImageHandler = (e) => {
        const file = e.target.files[0];
        const reader = new FileReader();

        reader.readAsDataURL(file);
        reader.onload = () => {
            setImagePrev(reader.result);
            setImage(file);
        };
    };

    const submitHandler = async (e) => {
        e.preventDefault();

        const formData = new FormData();
        formData.append("title", title);
        formData.append("description", description);
        formData.append("category", category);
        formData.append("createdBy", createdBy); // Assuming `createdBy` is the instructor's ID
        formData.append("file", image);

        try {
            const { data } = await axios.post(
                "http://localhost:4000/api/v1/createcourse",
                formData,
                {
                    withCredentials: true,
                    headers: {
                        "Content-Type": "multipart/form-data",
                    },
                }
            );

            console.log(data.message);
            setTitle("");
            setDescription("");
            setCreatedBy("");
            setCategory("");
            setImage(null);
            setImagePrev("");
        } catch (error) {
            console.error(error.response.data.message);
        }
    };

    return (
        <Grid minH={"100vh"} templateColumns={["1fr", "5fr 1fr"]}>
            <Container py={"16"}>
                <form onSubmit={submitHandler}>
                    <Heading
                        textTransform={"uppercase"}
                        my={16}
                        textAlign={["center", "left"]}
                    >
                        Create Course
                    </Heading>
                    <VStack m={"auto"} spacing={"8"}>
                        <Input
                            value={title}
                            onChange={(e) => setTitle(e.target.value)}
                            placeholder="Title"
                            type="text"
                            focusBorderColor="purple.300"
                        />
                        <Input
                            value={description}
                            onChange={(e) => setDescription(e.target.value)}
                            placeholder="Description"
                            type="text"
                            focusBorderColor="purple.300"
                        />
                        <Select
                            focusBorderColor="purple.300"
                            value={createdBy}
                            onChange={(e) => setCreatedBy(e.target.value)}
                            placeholder="Select Instructor"
                        >
                            {instructors.map((instructor) => (
                                <option key={instructor._id} value={instructor._id}>
                                    {instructor.name}
                                </option>
                            ))}
                        </Select>
                        <Select
                            focusBorderColor="purple.300"
                            value={category}
                            onChange={(e) => setCategory(e.target.value)}
                        >
                            <option value="">Category</option>
                            {categories.map((item) => (
                                <option key={item} value={item}>
                                    {item}
                                </option>
                            ))}
                        </Select>

                        <Input
                            accept="image/*"
                            css={{
                                "&::file-selector-button": {
                                    color: "purple",
                                },
                            }}
                            required
                            id="chooseAvatar"
                            type="file"
                            focusBorderColor="purple.300"
                            onChange={changeImageHandler}
                        />
                        {imagePrev && (
                            <Image src={imagePrev} boxSize="64" objectFit={"contain"} />
                        )}
                        <Button w={"full"} colorScheme="purple" type="submit">
                            Create
                        </Button>
                    </VStack>
                </form>
            </Container>
            <Sidebar />
        </Grid>
    );
};

export default CreateCourses;

Start your frontend using the below command:

npm start

Output

Comment

Explore