Here's a quick summary of everything we released in Q1 2024.

How to build a job aggregator with no backend code

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.
Ashutosh Singh

Ashutosh Singh

Mar 07, 2023
How to build a job aggregator with no backend code

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.

Graphic_ how to build a job aggregator.png

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.

Hygraph sign-up

On your Hygraph dashboard, click Add project to create a new blank project.

Add 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.

New 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.

Hygraph dashboard

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.

New model

Next, you'll add fields to this Page model. From the right sidebar, click on the Single line text field.

Page model

You'll be prompted to add a Display name to this field. Add "Title" as the display name and click Add.

Title field

Now, click on Rich text on the right sidebar, enter "Body" as the Display name of this field, and click Add to save it.

Body field

Your Page model will look something like this:

Page model

#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.

Add remote source

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.

Base URL

The next step is to add a custom type definition to define the structure of REST API. Click on + Add custom type definition.

Custom type

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: String
jobCount: Int
jobs: [Job]
}

Query type

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: Int
url: String
title: String
company_name: String
company_logo: String
category: String
job_type: String
publication_date: String
candidate_required_location: String
salary: String
description: String!
tags: [String]
}

Here is how this will look in the Hygraph dashboard:

Job type

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.

REST field

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.

Jobs field

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.

Add entry

Enter the following data:

  • Title: "Svelte + Hygraph Job Aggregator"
  • Body: "Find the Best Remote Jobs"

After adding the data, click Save & publish.

Adding new content to the Page model

Now, you will query this data in the API playground. Click on API playground tab in the left sidebar.

API playground

Add the following GraphQL query, and hit Run:

query Pages {
pages {
id
title
body {
html
}
jobsQuery {
jobs {
id
url
title
company_name
company_logo
category
job_type
publication_date
candidate_required_location
salary
tags
}
}
}
}

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.

API playground

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.

API access

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.

Permanent auth token

When prompted for the Token name, enter "Svelte Job Aggregator" and click Add & Configure permissions.

Svelte job aggregator token

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.

Token

On the same page, scroll down to the Content API section and click Yes, initialize defaults to add Read permissions to this token.

Content API

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 app
Library 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-aggregator
npm install -D tailwindcss postcss autoprefixer
npx 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 @tailwinddirectives 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 dotenv
touch .env

Paste your content API endpoint and auth token from Hygraph in this .env file:

HYGRAPH_CONTENT_ENDPOINT = YOUR-HYGRAPH-CONTENT-API-ENDPOINT
HYGRAPH_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 {
id
title
body {
html
}
jobsQuery {
jobs {
id
url
title
company_name
company_logo
category
job_type
publication_date
candidate_required_location
salary
tags
}
}
}
}
`;
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)}
<div
key={job.id}
class="shadow-md border border-gray-700 overflow-hidden sm:rounded-lg mt-6 p-6"
>
<div
class="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">
<img
src={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}
<div
class="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}
<div
class="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>
<a
href={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}
<div
class="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 extract pages 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 the body on your app. We can use the {@html ...} tag to render html from body.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:

App preview

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">
<input
type="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">
<svg
xmlns="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"
>
<path
stroke-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:

App with search bar

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:

App GIF

#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.

Blog Author

Ashutosh Singh

Ashutosh Singh

Ashutosh is a writer, learner, and JavaScript developer who enjoys writing articles that help people.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.