Job Search App (API) with HTML, CSS & JavaScript
30 DAYS 30 PROJECT CHALLENGE
Day #26
Project Overview
The Job Search App is a beginner-friendly project built with HTML, CSS, and JavaScript that allows users to search for remote job listings using keywords. It connects to the Remotive Jobs API and retrieves job data based on the search term entered by the user. Users can search for roles, skills, or keywords such as frontend, React, or developer. After the search, JavaScript processes the API response and displays the available jobs in individual cards. Each card shows the job title, company name, required location, and a direct View Job link.
It also shows loading message while the API request runs and handles cases where no jobs are available or the request fails. This project gives beginners practical experience with API integration, asynchronous data fetching, and error handling.
Key Features
- Job Search: This project provides users to search for jobs by entering a role, skill, or keyword such as frontend, React, or developer.
- Jobs API Integration: It connects to the Remotive Jobs API to retrieve remote job listings. Therefore, users can work with externally provided job data instead of fixed information.
- Keyword-Based Results: JavaScript sends the user’s search term to the API using a query parameter. As a result, the returned listings can match the specific keyword entered by the user.
- Default Search: If the search field remains empty, the application automatically uses
"developer"as the search term. This ensures that the app can still request job listings without requiring input. - Dynamic Job Cards: The returned jobs appear as individual cards inside the
#jobListelement. Each card is created dynamically from the information received from the API. - Job Title Display: Every job card shows the title returned by the API. This helps users quickly understand the position available in each listing.
- Company Information: The application displays the company name for every returned job and gives users additional context before they open the full listing.
- Location Details: Each job card includes the
candidate_required_locationvalue from the API response. Therefore, users can quickly see the location requirements associated with the position. - View Job Links: Every result includes a direct View Job link using the job’s API-provided URL, whick opens the link in a new browser tab so users can view the full listing separately.
- Limited Results: The application displays only the first five jobs using
slice(0, 5). This keeps the result area compact and prevents too many listings from appearing at once. - Loading State: The app displays
"Loading..."immediately after a search begins. This gives users clear feedback while the API request is processing. - Error Handling: The
.catch()block handles failed API requests and displays"Error loading jobs". This makes the interface more useful when the external service cannot be reached. - Clean Interface: The project uses a centered card, combined search controls, and separate job cards. Therefore, users can search and scan the available listings without a complicated interface.
What You'll Learn
- How to connect a frontend application with a public jobs API.
- How to use
fetch()to retrieve external job data. - How to create API URLs dynamically with template literals.
- How
encodeURIComponent()prepares search terms for a URL. - How to process JSON data returned from an API.
- How to check whether an API response contains job listings.
- How to use
slice()to limit the number of displayed results. - How to use
forEach()to process multiple jobs. - How to dynamically create HTML with template literals.
- How to display API data using
innerHTML. - How to create external links dynamically from API data.
- How to handle loading, empty-result, and error states.
HTML Code
HTML creates the basic structure of the Job Search App. The .job-container holds the complete application, while the <h2> displays the Job Search heading. Inside the .search-box, the #searchInput field allows users to enter a job keyword, and the Search button calls getJobs() when clicked.
Additionally, the #jobList div starts empty and provides the area where JavaScript displays the returned job cards. Finally, the page loads script.js after the main HTML structure so the JavaScript can access the search field and results container.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Job Search App</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="job-container">
<h2>Job Search</h2>
<div class="search-box">
<input
type="text"
id="searchInput"
placeholder="Search jobs (e.g. frontend, react)"
/>
<button onclick="getJobs()">Search</button>
</div>
<div id="jobList" class="job-list"></div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS Code
CSS controls the layout and appearance of the Job Search App. The universal * selector sets box-sizing: border-box and applies the Segoe UI font. Meanwhile, the body uses Flexbox to center the application horizontally and adds top spacing so the container starts neatly near the top of the page. This .job-container creates the main white card with a fixed width, padding, rounded corners, and a shadow.
Next, .search-box uses Flexbox to keep the input and button together, while flex: 1 allows the input to use the available space and the .job-list arranges the job cards vertically with consistent gaps. Finally, .job-card adds borders, padding, and rounded corners to separate each listing, while the .job-card a styles the View Job link. The .error class centers error messages and displays them in red.
* {
box-sizing: border-box;
font-family: "Segoe UI", sans-serif;
}
body {
min-height: 100vh;
background: #042453;
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 40px;
}
.job-container {
background: #ffffff;
width: 440px;
padding: 25px;
border-radius: 14px;
box-shadow: 0 20px 40px rgba(0,0,0,0.25);
}
h2 {
text-align: center;
margin-bottom: 15px;
}
.search-box {
display: flex;
margin-bottom: 15px;
}
.search-box input {
flex: 1;
padding: 10px;
border-radius: 6px 0 0 6px;
border: 1px solid #ccc;
}
.search-box button {
padding: 10px 16px;
border: none;
border-radius: 0 6px 6px 0;
background: #4f46e5;
color: white;
cursor: pointer;
}
.job-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.job-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 12px;
}
.job-card h4 {
font-size: 15px;
margin-bottom: 6px;
}
.job-card p {
font-size: 13px;
color: #555;
margin-bottom: 6px;
}
.job-card a {
font-size: 13px;
color: #4f46e5;
text-decoration: none;
}
.error {
color: red;
text-align: center;
}
Javascript Code
JavaScript handles the complete job-search process. The getJobs() function first reads and trims the value from #searchInput. If the field is empty, it uses "developer" as the default query. Next, it displays "Loading..." and sends a request to the Remotive Jobs API using fetch() and encodeURIComponent(query).
After the API returns a response, res.json() converts it into JavaScript data. The code then checks whether data.jobs exists and contains results. If no jobs are available, it displays "No jobs found" and stops the function.
When jobs are available, the code clears the loading message and uses data.jobs.slice(0, 5).forEach() to process the first five listings. For each job, it creates a card containing job.title, job.company_name, job.candidate_required_location, and job.url. Finally, the .catch() block handles failed requests and displays "Error loading jobs" in the result area.
const jobList = document.getElementById("jobList");
function getJobs() {
const query = document.getElementById("searchInput").value.trim() || "developer";
jobList.innerHTML = "Loading...";
fetch(`https://remotive.com/api/remote-jobs?search=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
if (!data.jobs || data.jobs.length === 0) {
jobList.innerHTML = `<p class="error">No jobs found</p>`;
return;
}
jobList.innerHTML = "";
data.jobs.slice(0, 5).forEach(job => {
jobList.innerHTML += `
<div class="job-card">
<h4>${job.title}</h4>
<p><strong>Company:</strong> ${job.company_name}</p>
<p><strong>Location:</strong> ${job.candidate_required_location}</p>
<a href="${job.url}" target="_blank">View Job</a>
</div>
`;
});
})
.catch(() => {
jobList.innerHTML = `<p class="error">Error loading jobs</p>`;
});
}
Related Projects
Day 24 : Crypto Price Tracker
Displays real-time cryptocurrency prices using a public crypto API.
Concepts: API integration, async data fetching, dynamic UI updates.
Day 28 : Language Translator
Translates text between multiple languages using a public translation API.
Concepts: API integration, async data handling, form handling, dynamic UI updates.
Day 30 : Public Holiday Finder
Finds and displays public holidays by country and year using a public API.
Concepts: API integration, async data fetching, dynamic UI rendering.