DEV Community

Cover image for How to Create Your Own Job Board Web App Using React.js, Node.js, SerpApi, and MUI
Jagroop Singh
Jagroop Singh

Posted on

How to Create Your Own Job Board Web App Using React.js, Node.js, SerpApi, and MUI

In this blog, we'll walk through building a job board web app using React.js (with Vite for setup), Node.js (using Express), SerpApi to fetch job listings from Google Jobs, and Material-UI (MUI) for styling.

By the end of this tutorial, you'll have a functional job board where users can search for jobs and view results fetched from Google's job listings.

Here is the demo of this project :

demo1

demo2

demo3

demo4

Prerequisites
To follow along, you'll need:

  • Basic knowledge of React.js and Node.js
  • Node.js installed
  • SerpApi account and API key
  • Vite for project setup
  • MUI for styling

1. Let's create Account on SerpAPI website :

Website Link : https://serpapi.com/

SerpAPI Website

One can either create an account or login ( if account already existed)

Next, select the API Key Section on the left-side bar and choose to generate a new key, use an existing one, or create a new one.

Getting API key

2. Project Structure

Here is the final project structure with both the server and client looks like:

job-board/
│
├── job-board-client/            # Frontend (React + Vite)
│   ├── node_modules/
│   ├── public/
│   ├── src/
│   │   ├── components/
│   │   │   ├── SearchBar.jsx     # Search bar component
│   │   │   ├── JobList.jsx       # Job list display component
│   │   ├── App.jsx               # Main App component
│   │   ├── main.jsx              # Entry point for the React app
│   │   └── index.css             # Global CSS
│   ├── .gitignore
│   ├── index.html                # Main HTML file
│   ├── package.json              # Dependencies and scripts
│   ├── vite.config.js            # Vite configuration
│   └── README.md
│
├── job-board-server/             # Backend (Node.js + Express)
│   ├── node_modules/
│   ├── index.js                  # Express server entry point
│   ├── .env                      # Environment variables (e.g., SERP_API_KEY)
│   ├── package.json              # Dependencies and scripts
│   ├── .gitignore
│   └── README.md
Enter fullscreen mode Exit fullscreen mode

3. Create the root folder job-board

mkdir job-board
cd job-board/

Enter fullscreen mode Exit fullscreen mode

4. Initialize the React Frontend (Vite + React.js)

Start by setting up the React project with Vite.

# Create React project using Vite
npm create vite@latest job-board-client --template react

# Navigate into the project
cd job-board-client

# Install dependencies
npm install

Enter fullscreen mode Exit fullscreen mode

Step1

Step 2

Install Material-UI (MUI) for styling and axios for api calling.

# MUI Core and icons for styling
npm install axios @mui/material @emotion/react @emotion/styled @mui/icons-material

Enter fullscreen mode Exit fullscreen mode

5. Initialize the Node.js Backend (Express)

Next, create a backend folder and initialize an Express server.In order to create backend you must be at job-board. One can do this by simply running this command cd ...
After that run,

# Create backend directory
mkdir job-board-server
cd job-board-server

# Initialize Node.js project
npm init -y

# Install Express
npm install express cors axios dotenv

Enter fullscreen mode Exit fullscreen mode

3

4

6. Set up a basic Express server (index.js) in the job-board-server directory.

In the job-board-server folder create index.js file and create api that return jobs.

const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();

const app = express();
app.use(cors());

const PORT = process.env.PORT || 5000;

// Endpoint to fetch job listings
app.get('/api/jobs', async (req, res) => {
  const { query } = req.query;

  try {
    const serpApiUrl = `https://serpapi.com/search.json?engine=google_jobs&q=${query}&api_key=${process.env.SERP_API_KEY}`;
    const response = await axios.get(serpApiUrl);
    res.json(response.data.jobs_results || []);
  } catch (error) {
    res.status(500).json({ error: 'Error fetching jobs' });
  }
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Enter fullscreen mode Exit fullscreen mode

7. Add Environment Variables

Create a .env file in your job-board-server directory and add your SerpApi API key.

SERP_API_KEY=your_serp_api_key_here

Enter fullscreen mode Exit fullscreen mode

8. Frontend: Job Search UI (React + MUI)

In the React project, create a search component that allows users to input search terms and view job results.

  1. Create a SearchBar component in src/components/SearchBar.jsx.
import React, { useState } from 'react';
import { TextField, Button, CircularProgress } from '@mui/material';

const SearchBar = ({ onSearch, loading }) => {
  const [query, setQuery] = useState('');

  const handleSearch = () => {
    if (query.trim()) {
      onSearch(query);
    }
  };

  return (
    <div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px' }}>
      <TextField
        label="Search Jobs"
        variant="outlined"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        style={{ marginRight: '10px', width: '300px' }}
      />
      <Button 
        variant="contained" 
        color="primary" 
        onClick={handleSearch} 
        disabled={loading}
      >
        {loading ? <CircularProgress size={24} /> : 'Search'}
      </Button>
    </div>
  );
};

export default SearchBar;

Enter fullscreen mode Exit fullscreen mode
  1. Create a JobList component in src/components/JobList.jsx to display job results.
import React from 'react';
import { Card, CardContent, CardActions, Typography, Button, Grid, CircularProgress, Box } from '@mui/material';
import { WorkOutline } from '@mui/icons-material';

const JobCard = ({ job }) => {
    return (
        <Card
            sx={{
                display: 'flex',
                flexDirection: 'column',
                justifyContent: 'space-between',
                // height: '100%',
                boxShadow: '0 4px 8px #1976d2',
                p: 2,
                mt:3
            }}>
            <CardContent>
                <Typography variant="h5" component="div">
                    {job.title}
                </Typography>
                <Typography sx={{ mb: 1.5 }} color="text.secondary">
                    {job.company_name} - {job.location}
                </Typography>
                <Typography variant="body2">
                    {job.description.slice(0, 150)}... {/* Preview part of description */}
                </Typography>
            </CardContent>
            <CardActions>
                <Button
                    sx={{
                        backgroundColor: '#1976d2',
                        color: '#fff',
                        '&:hover': {
                            backgroundColor: '#1565c0',
                        },

                        width: '100%',
                    }}
                    size="small" href={job.share_link} target="_blank" rel="noopener">
                    Apply
                </Button>
            </CardActions>
        </Card>
    );
};

const JobList = ({ jobs, loading }) => {

    if (loading) {
        return (
          <Box display="flex" justifyContent="center" marginTop="20px">
            <CircularProgress />
          </Box>
        );
      }

      if (jobs.length === 0) {
        return (
          <Box display="flex" justifyContent="center" alignItems="center" flexDirection="column" marginTop="20px">
            <WorkOutline style={{ fontSize: 60, color: 'gray' }} />
            <Typography variant="h6" color="textSecondary">
              No jobs available
            </Typography>
          </Box>
        );
      }

    return (
        <Grid container spacing={2}>
            {jobs.map((job, index) => (
                <Grid item xs={12} sm={6} md={4} key={index}>
                    <JobCard job={job} />
                </Grid>
            ))}
        </Grid>
    );
};

export default JobList;

Enter fullscreen mode Exit fullscreen mode
  1. In the App.jsx, bring everything together.
import React, { useState } from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import SearchBar from './components/SearchBar';
import JobList from './components/JobList';
import axios from 'axios';
import { Container } from '@mui/material';

const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#ff4081',
    },
  },
});

const App = () => {
  const [jobs, setJobs] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleSearch = async (query) => {
    try {
      setLoading(true);
      const response = await axios.get(`http://localhost:5000/api/jobs`, {
        params: { query }
      });
      setJobs(response.data);
      setLoading(false);
    } catch (error) {
      console.error('Error fetching job listings:', error);
      setLoading(false);
    }
  };

  return (
    <ThemeProvider theme={theme}>
      <Container>
      <SearchBar onSearch={handleSearch} loading={loading} />
      <JobList jobs={jobs}  loading={loading} />
      </Container>
    </ThemeProvider>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

9. Start the Servers

Make sure both the backend and frontend are running.

# In the job-board-server folder
node index.js

Enter fullscreen mode Exit fullscreen mode
# In the job-board-client folder
npm run dev

Enter fullscreen mode Exit fullscreen mode

🎉 You’ve now built a fully functional job board web app! The frontend is built using React.js ⚛️ and styled with Material-UI 🎨, while the backend uses Node.js 🚀 with Express to serve job listings from SerpApi 🌐.

This is a great starting point for more advanced features, such as filtering by location 📍, adding job detail pages 📄, or even allowing users to save job listings 💾.

That's all for this blog! Stay tuned for more updates and keep building amazing apps! 💻✨
Happy coding! 😊

Top comments (8)

Collapse
 
works profile image
Web

@jagroop2001 , You come up with fresh concepts and technologies every week. Your blogs are a great resource for me to learn from.

In my opinion, you should begin offering lessons on YouTube in order to cover more complex projects.

Collapse
 
jagroop2001 profile image
Jagroop Singh

@works ,
Thank you so much for your kind words! I'm glad to hear that you find my blogs helpful.
The idea of offering lessons on YouTube is exciting! It would definitely allow for a deeper dive into complex projects and concepts.
I’ll definitely consider it as I continue to explore new technologies. Stay tuned for updates, and thanks again for your support!

Collapse
 
john12 profile image
john

@jagroop2001 , SerpAPI is free or we can only search jobs or something else

Collapse
 
jagroop2001 profile image
Jagroop Singh

@john12 , It have functionalities of maps, books,shopping,lens finance,scholar and lot's of things that's available in google.

Collapse
 
john12 profile image
john

What's about hotels features. Does it have that capabilities ?

Thread Thread
 
jagroop2001 profile image
Jagroop Singh

Yes @john12 , it's available.

Image description

Your can explore more by creating an account.

Thread Thread
 
john12 profile image
john

@jagroop2001 , thanks.
It's a great article. With the reference of this article I will try to create Hotel Search Web App with custom filter's

Thread Thread
 
jagroop2001 profile image
Jagroop Singh

@john12 , Sure and let me know if you need any help from my end.