A job aggregator is a website or platform that gathers job listings from various sources on the web and presents them in one place, which makes it easier for job seekers to search and find job opportunities.
However, gathering these listings from multiple sources—including job boards, recruitment agencies, and company websites—and integrating them into a unified format can be a tedious and time-consuming task to set up and maintain manually. After all, once it's ready to go, you also need to make sure that the job listings remain up-to-date and accurate.
Luckily, there are tools that can help you out. Hygraph, for example, is a GraphQL content platform that has many use cases, such as in e-commerce, inventory and catalog management, as well as structuring and organizing content. For developing a job aggregator app, it offers Remote Sources, a content federation utility that allows you to quickly and easily add content from multiple sources, such as job boards, and create a single GraphQL API.
In this tutorial, you'll learn how to create a job aggregator without any backend code using Hygraph and SvelteKit. You'll also use Tailwind CSS, a utility-first CSS framework, for styling the app.
Check out the GitHub repo if you want to jump right into the code. And here's a link to the deployed version.
#Building a job aggregator with Hygraph
In this tutorial, you'll build a job aggregator that sources job listings from Remotive. You'll also create a Page data model. The job listings will then be fetched from the Remotive API and displayed on the SvelteKit frontend using the data fetched from the Hygraph GraphQL API.
Prerequisites
To follow along with this tutorial, you need the following:
- Working knowledge of HTML, CSS, and JavaScript
- Node.js and npm installed on your local dev machine
- Any code editor of your choice (for example, VS Code)
Setting up Hygraph
This section outlines the process of setting up Hygraph and creating a project for a job aggregator application. The first step is to create a Hygraph account, which you'll use to create the project and run the job aggregator application.
First, create a free Hygraph account if you don’t already have one.
On your Hygraph dashboard, click Add project to create a new blank project.
You will be prompted to enter some details. Name the project "Job Aggregator" and choose the region closest to your location. You can also optionally add a project description. Click Add Project to create the project.
Create a model in Hygraph
In this section, you'll create a model for our application in Hygraph. A model is a blueprint or schema that defines the structure of our application's data. By creating a model, you provide a structure for our data and ensure that it conforms to a specific format.
Head over to your Hygraph dashboard and click on the Schema tab on the left sidebar.
Click on + Add on the left sidebar next to Models and name the model “Page”. Click Add Model on the bottom right, after naming the model to save it.
Next, you'll add fields to this Page model. From the right sidebar, click on the Single line text field.
You'll be prompted to add a Display name to this field. Add "Title" as the display name and click Add.
Now, click on Rich text on the right sidebar, enter "Body" as the Display name of this field, and click Add to save it.
Your Page model will look something like this:
#Adding a Remote Source
In this section, you'll learn how to add the Remotive API as a remote source to fetch job listings for your application. By doing so, you can retrieve data from an external API and use it in your Hygraph application.
Click Add next to the Remote Sources tab in the left sidebar. When prompted, name the Remote Source "Remotive" and select Type as REST.
You will be asked for the Base URL, which is https://remotive.com/api/remote-jobs?limit=5.
To ensure that the app runs quickly for this tutorial, the response from the Remotive API is limited to five jobs. However, in a real-world application, you would typically retrieve a larger dataset and paginate the results to improve performance.
The next step is to add a custom type definition to define the structure of REST API. Click on + Add custom type definition.
Add the following code to create a type named Query
in the custom type definition section for the response from Remotive API:
type Query {legalNotice: StringjobCount: Intjobs: [Job]}
You also need to create another type for the actual job object present in the Query
type. Create another type named Job
by clicking on + Add custom type definition and add the following code to it:
type Job {id: Inturl: Stringtitle: Stringcompany_name: Stringcompany_logo: Stringcategory: Stringjob_type: Stringpublication_date: Stringcandidate_required_location: Stringsalary: Stringdescription: String!tags: [String]}
Here is how this will look in the Hygraph dashboard:
Click on Add on the top right to save this Remote Source. Now, you'll add this Remotive remote source to your Page Model. Click on the Page model on the left sidebar. On the right sidebar, scroll down to the bottom. Click the field named REST.
You'll be prompted to enter details about this field:
- Display name: "JobsQuery"
- API ID: "jobsQuery" (this will be auto-generated)
- Remote source: "Remotive"
- Method:"GET"
- Return type: "Query" (the custom type created in the previous steps)
You can leave the other fields blank.
Click Add to save this field. You've successfully added the Remotive remote source to your Page model.
#Adding data to content type
You now have the Page model that defines the structure of the content, but you're missing the actual content that will be shown on the front end. In this section, you'll add data to the fields in the Page model you created previously and test it in the API playground.
Click on Content in the left sidebar and click + Add entry on the top right.
Enter the following data:
- Title: "Svelte + Hygraph Job Aggregator"
- Body: "Find the Best Remote Jobs"
After adding the data, click Save & publish.
Now, you will query this data in the API playground. Click on API playground tab in the left sidebar.
Add the following GraphQL query, and hit Run:
query Pages {pages {idtitlebody {html}jobsQuery {jobs {idurltitlecompany_namecompany_logocategoryjob_typepublication_datecandidate_required_locationsalarytags}}}}
Below is how the playground looks after the successful execution of the query. The response can be viewed on the right side of the playground, and the jobs
array fetched from Remotive API is located under the jobsQuery
field.
Setting up permissions for the Content API
In this step, you'll set up permissions for the Content API so that your data can be accessed only through authenticated requests. Content API is a read-and-write endpoint that allows querying and mutating data in your project as well as caching it for best performance, and setting up permissions which allows you to control who can access your data and what actions they can perform on it.
Click Project settings in the left sidebar and then click API Access, as shown below. You will see all the endpoints for your project, such as Content API, Management API, etc. You will use the Content API to fetch your data from Hygraph and copy this endpoint.
Next, you'll create an auth token for your project and use it to make authenticated requests to your Content API. Click on Permanent Auth Tokens in the left sidebar and then click + Add token.
When prompted for the Token name, enter "Svelte Job Aggregator" and click Add & Configure permissions.
Your token will look something like the screenshot below. Copy this token, as it will be later used on the frontend to fetch data from the Content API.
On the same page, scroll down to the Content API section and click Yes, initialize defaults to add Read permissions to this token.
The no-code backend setup with Hygraph is now complete.
Setting up SvelteKit with Tailwind CSS
The next step is to create the frontend of this app using SvelteKit and Tailwind CSS.
First, you'll initialize a SvelteKit project. In your project's root directory, run the following commands in the terminal:
npm init svelte@latest svelte-hygraph-job-aggregator
When prompted for Svelte app template, select Skeleton project:
? Which Svelte app template? › - Use arrow-keys. Return to submit.SvelteKit demo app❯ Skeleton project - Barebones scaffolding for your new SvelteKit appLibrary skeleton project
Select No for other configs:
✔ Which Svelte app template? › Skeleton project✔ Add type checking with TypeScript? › No✔ Add ESLint for code linting? … No / Yes✔ Add Prettier for code formatting? … No / Yes✔ Add Playwright for browser testing? … No / Yes✔ Add Vitest for unit testing? … No / Yes
Run the following commands to install Tailwind CSS to the SvelteKit project:
cd svelte-hygraph-job-aggregatornpm install -D tailwindcss postcss autoprefixernpx tailwindcss init tailwind.config.cjs -p
Update the svete.config.js
file like this:
import adapter from "@sveltejs/adapter-auto";import { vitePreprocess } from "@sveltejs/kit/vite";/** @type {import('@sveltejs/kit').Config} */const config = {kit: {adapter: adapter(),},preprocess: vitePreprocess(),};export default config;
Modify the tailwind.config.cjs
file like this:
/** @type {import('tailwindcss').Config} */module.exports = {content: ["./src/**/*.{html,js,svelte,ts}"],theme: {extend: {},},plugins: [],};
The next step is to create an app.css
file in the src
directory. Run the following command in the project root directory to create the file:
touch src/app.css
Now, add the @tailwind
directives to app.css
:
@tailwind base;@tailwind components;@tailwind utilities;
Run the following command to create a +layout.svelte
file in the src/routes
folder:
touch src/routes/+layout.svelte
Add the following code to +layout.svelte
file to import the newly created app.css
file:
<script>import "../app.css";</script><slot />
Run the following commands to install dotenv
and create a file named .env
to securely store our Content API endpoint and auth token as environment variables:
npm i dotenvtouch .env
Paste your content API endpoint and auth token from Hygraph in this .env
file:
HYGRAPH_CONTENT_ENDPOINT = YOUR-HYGRAPH-CONTENT-API-ENDPOINTHYGRAPH_CONTENT_TOKEN = YOUR-HYGRAPH-CONTENT-TOKEN
Fetch data using SvelteKit server-only modules
After initializing our Svelte project, the next step is to display data from Hygraph. This will allow us to build our job aggregator app.
Run the following command to install graphql
and graphql-request
:
npm i graphql-request graphql
Run the following command to create a server-only module in the routes
directory:
touch src/routes/+page.server.js
Add the following code to the +page.server.js
file:
import { GraphQLClient, gql } from "graphql-request";import {HYGRAPH_CONTENT_ENDPOINT,HYGRAPH_CONTENT_TOKEN,} from "$env/static/private";export async function load() {const hygraph = new GraphQLClient(HYGRAPH_CONTENT_ENDPOINT, {headers: {Authorization: `Bearer ${HYGRAPH_CONTENT_TOKEN}`,},});const QUERY = gql`{pages {idtitlebody {html}jobsQuery {jobs {idurltitlecompany_namecompany_logocategoryjob_typepublication_datecandidate_required_locationsalarytags}}}}`;const { pages } = await hygraph.request(QUERY);return {pages,};}
Here's what's going on in the code snippet above:
First, you start by importing GraphQLClient
and gql
from graphql-request
. You also import your environment variables from $env/static/private
.
You create a load
function containing a GraphQL client for your Content API endpoint. You then pass the Content API endpoint and auth token to the GraphQLClient
method.
Next, you create the GraphQL query using the gql
syntax. You fetch all the data necessary to construct the job listing, which you can customize to meet your needs.
Finally, you make the API request and return the data from the load()
function.
#Displaying jobs on the frontend
In this section, you'll create job listings based on the data returned by the server-only module.
First, update the +page.svelte
file like this:
<script>export let data;let pages = data.pages;let { title, body, jobsQuery } = pages[0];let jobs = jobsQuery.jobs;</script><svelte:head><title>Svelte + Hygraph Job Aggregator</title></svelte:head><main class="max-w-4xl min-h-screen mx-auto py-6 px-4 sm:px-6 lg:px-8"><div class="text-center py-12"><h1 class="text-4xl lg:text-5xl font-medium text-gray-900">{title}</h1><div class="text-md lg:text-xl text-gray-600 my-2">{@html body.html}</div></div><div class="my-6"><div class="mt-6">{#each jobs as job (job.id)}<divkey={job.id}class="shadow-md border border-gray-700 overflow-hidden sm:rounded-lg mt-6 p-6"><divclass="flex flex-col lg:flex-row lg:items-center justify-between space-x-4"><div class="flex items-center space-x-6 md:w-1/2"><imgsrc={job.company_logo}alt={job.company_name}class="w-12 h-12 rounded-full"/><div><h4 class="text-xl font-medium text-gray-700">{job.title}</h4><div class="text-gray-700">{job.company_name}</div></div></div><div class="flex md:flex-col mt-4 lg:mt-0 items-start">{#if job.salary}<divclass="text-gray-700 bg-green-100 text-green-800 font-semibold px-3 py-1 text-center rounded-full inline-block uppercase text-xs">${job.salary}</div>{/if}{#if job.candidate_required_location}<divclass="text-gray-700 ml-2 md:ml-0 md:mt-2 bg-pink-100 text-pink-800 font-semibold text-center px-3 py-1 rounded-full inline-block uppercase text-xs">{job.candidate_required_location}</div>{/if}</div><ahref={job.url}class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-center uppercase text-xs mt-4 lg:mt-0">Apply</a></div>{#if job.tags}<div class="flex flex-wrap mt-6">{#each job.tags as tag}<divclass="text-gray-700 bg-gray-100 text-gray-800 font-semibold px-3 py-1 mr-2 mb-2 text-center rounded-full inline-block uppercase text-xs">{tag}</div>{/each}</div>{/if}</div>{/each}</div></div></main>
Here's what's going on in the code snippet above:
- First, you export a variable named
data
. This data is updated with the response from the load function in the+page.svelte
file so you can extractpages
from it:
<script>export let data;let pages = data.pages;let { title, body, jobsQuery } = pages[0];let jobs = jobsQuery.jobs;</script>
You then extract the title
, body
, and jobsQuery
from the first element of the pages
array. You also create jobs
variable which holds the actual jobs listing data from the jobsQuery
variable.
- Next, you display the
title
and thebody
on your app. We can use the{@html ...}
tag to render html frombody.html
directly in your app:
<div class="text-center py-12"><h1 class="text-4xl lg:text-5xl font-medium text-gray-900">{title}</h1><div class="text-md lg:text-xl text-gray-600 my-2">{@html body.html}</div></div>
Note: You can refer to the Svelte documentation if you want to learn more about HTML tags.
- Finally, you map over the
jobs
array to display each job in your app:
{#each jobs as job (job.id)}...{/each}
Run the following command to start your development server:
npm run dev
Navigate to http://localhost:5173/ in your browser to see what your app looks like. It should be something like this:
Add a search bar to your job aggregator
In this section, you'll add a search bar to your app. The search bar allows users to search for specific jobs based on keywords.
Update the script
tag to add another variable named value
and initiate it with an empty string:
<script>export let data;let pages = data.pages;let { title, body, jobsQuery } = pages[0];let jobs = jobsQuery.jobs;let value = '';</script>
Add the following code above the {#each jobs as job (job.id)}
line:
<div class="relative"><inputtype="text"class="w-full border border-gray-700 rounded-md pl-10 pr-4 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"placeholder="Search for a job"bind:value/><div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"><svgxmlns="http://www.w3.org/2000/svg"fill="none"viewBox="0 0 24 24"stroke-width="1.5"stroke="currentColor"class="w-6 h-6 text-gray-400"><pathstroke-linecap="round"stroke-linejoin="round"d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"/></svg></div></div>
In this code block, you create an input field and bind its value to the value
variable.
Your app will look something like this:
Now, you have a static search bar, but typing in it doesn’t do anything. You need to create a function that filters the jobs based on the input in the search bar. Update the script
tag like this:
<script>export let data;let pages = data.pages;let { title, body, jobsQuery } = pages[0];let jobs = jobsQuery.jobs;let value = "";$: filtered = jobs.filter((item) =>item.title.toLowerCase().includes(value.toLowerCase()));</script>
Here, you're making use of Svelte's reactivity to filter out jobs with a position that includes the search term.
Update the loop over the jobs
array like this:
{#each value !== "" ? filtered : jobs as job (job.id)}
You can use the filtered
array instead of the jobs
array whenever the search term or value
is not equal to an empty string, i.e., the default state.
Here is the search function in action:
#Conclusion
In this tutorial, you learned how to build a job aggregator app with Hygraph and SvelteKit, two modern web development tools for fast and responsive web apps. Specifically, you created a comprehensive web app with no backend code using Hygraph's powerful API for web scraping and data collection capabilities, as well as SvelteKit to build server-side-rendered pages and APIs solely with frontend code. You also learned how to create server-only modules in SvelteKit. These modules let you do server-side tasks such as fetching data or interacting with APIs. These tools help you create a great user experience without having to worry about complex server setups. You used Remotive API as the job board API in this tutorial, but you can follow the same steps for other APIs such as Remote OK API or We Work Remotely API to build similar job aggregator apps.