Saturday, April 20, 2024
HomeJavaScriptConstruct a Job Board with React and GraphQL utilizing Webiny Headless CMS

Construct a Job Board with React and GraphQL utilizing Webiny Headless CMS


A job board is an app or part of an internet site the place job vacancies are marketed by employers to potential workers or freelancers. They largely embody options for trying to find and filtering jobs and a kind for making use of for a job. Examples of widespread job boards or platforms that embody a job board are Upwork, Certainly and Fiverr.

On this tutorial, we are going to be taught in regards to the Webiny CMS and the way we are able to use it in React by constructing a job board utility.

Conditions

To comply with together with this tutorial, it’s essential have the next:

Introduction to Webiny Headless CMS

A headless CMS is one which offers strictly with the content material. It’s a backend-only CMS the place the content material repository is separated from the presentation layer. As soon as created, its content material is served by way of an API giving front-end builders full management over the consumer expertise utilizing their native instruments.
The Webiny headless CMS is GraphQL based mostly, with a robust content material modeling function and it’s serverless, so optimized for scalability by default.

Create and Deploy a New Webiny Undertaking

To get began, we have to first create a Webiny challenge, deploy it after which mannequin our content material within the headless CMS utilizing the generated Admin app.

To deploy a brand new Webiny challenge, enter the next command within the terminal:

npx create-webiny-project webiny-backend

After operating the above command you can be requested a number of questions, considered one of which is to decide on a database, for that, choose DynamoDB which is appropriate for our use case. The above command will create a Webiny challenge known as webiny-backend which consists of three functions: a GraphQL API, an Admin app, and likewise a React web site.

As soon as the brand new challenge has been created, it’s time to deploy it into our AWS account. We will do that with the next instructions:

cd webiny-backend

yarn webiny deploy

If it’s our first deployment it would take over 20 minutes. after it’s completed the URL to the Admin App, GraphQL API endpoints and web site could be printed out within the terminal. We’ll use them shortly.

Defining Content material Mannequin within the Admin App

A content material mannequin defines the construction of the content material we need to retailer within the CMS. We will do that utilizing the generated Admin app. For our use case, we are going to outline a Job mannequin to carry job entries and an Software mannequin for job functions.

To entry the Admin app, we are able to click on the URL printed within the terminal earlier once we deployed our challenge.

Word: Maybe you may have closed the terminal, to view the URLs you’ll be able to the yarn webiny information command.




Output of the "yarn webiny info" command

For the primary time accessing the Admin app we might be prompted to create a default consumer with our particulars. After doing that, we shall be taken to the welcome web page:




Webiny admin panel welcome screen

Subsequent, navigate to the Content material Fashions web page by clicking on the New Content material Mannequin button inside the Headless CMS card, and on the following web page click on on New Modal on the high of the web page and we are going to see a immediate with a kind so as to add some data for a brand new Content material Mannequin.




new content model

For the Job mannequin, within the identify discipline enter Job then click on on the CREATE MODEL button. Following thesame course of let’s additionally create the Software mannequin. Every mannequin has a web page the place we are able to add completely different discipline sorts to it that are the illustration of the info to be held by the mannequin.




model field types

After making a mannequin we shall be taken to this web page. We will navigate to it by clicking on the edit icon of a mannequin on the Content material Fashions web page.

So as to add a discipline, we have to drag and drop it within the drop zone, then within the immediate that seems provide a Label (required) and click on on the SAVE FIELD button. Beneath is a video exhibiting how a TEXT discipline labeled title is added:

Now following the method within the Video above let’s create the next fields for the Job mannequin:

  • 4(4) TEXT fields with the labels: title, sort, station, and stage.
  • LONG TEXT discipline with the label description.




job content model

After creation, click on on the SAVE button on the high proper of the web page.

For the Software mannequin:

  • 4(4) TEXT fields the labels: identify, electronic mail, web site, and linkedInProfile.
  • NUMBER discipline with the label phoneNumber.
  • REFERENCE discipline with the label ref and Content material Fashions Job. This discipline shall be used to carry the ID of a JOB entry.




application content model

Then, click on on the SAVE button.

Now that we’ve got arrange a Webiny challenge and created our Content material fashions. We are actually left with consuming the GraphQL API within the front-end.

To check and introspect GraphQL queries and mutations on our content material fashions earlier than utilizing them from within the front-end, we are able to use the GraphQL playground. To entry the playground, click on on API Playground within the sidebar of the Admin app.

Constructing the Job Board Frontend

The app we are going to construct may have the next key options: A search bar for trying to find a job, filter choices for the job sort, expertise stage, and station (Distant/On-site), and a kind for making a job and making use of to at least one.

I’ve already created a job board template so we are able to solely concentrate on working with Webiny Headless CMS in creating the listed options/functionalities. The following step is to clone the GitHub repo.

Cloning the Starter Repo

We will do that with the next instructions:

git clone https://github.com/write-with-webiny.git

cd write-with-webiny/tutorials/react-job-board

npm set up

For working with GraphQL in our frontend we shall be utilizing Apollo consumer, I’ve included @apollo/consumer and graphql within the dependency object of the bundle.json file. So by operating the npm set up command the wanted packages shall be put in.

Now once we begin the app with npm begin, we must always see the next display in our browser:




List of available jobs

Proper now a job manually added to the app is being displayed, in a while this shall be changed with the one coming from the CMS.

Once we click on the Apply button on a job card, we are going to see the next:




Interface for applying for a job

And once we click on on the Submit Job button on the top-right of the web page, we are going to see the next:




Submit a new job

Initialize Apollo Consumer

To initializing Apollo we have to get an API token to allow us to achieve entry to the Webiny GraphQL API and the right Headless CMS GraphQL API endpoints. After getting the above we shall be storing them in a .env file in our app which is able to appear to be the next:

REACT_APP_WEBINY_GRAPHQL_TOKEN = your-token-here
REACT_APP_WEBINY_GRAPHQL_READ_URL = your-read-url-here
REACT_APP_WEBINY_GRAPHQL_MANAGE_URL = your-manage-url-here

To get the API token, head over to the Admin app and within the sidebar develop the Settings tab and we are going to see API keys below the ACCESS MANAGEMENT part.




api key menu item

Choose API keys, and on the following display click on on New API key. we are going to see a immediate the place we are able to set the identify, description, and entry management choices for our KEY.




Create API Key interface

Enter a reputation and outline then scroll down the web page, develop the Content material tab and choose All locales.




API Key all locales radio button

Subsequent, develop the Headless CMS tab which comprises choices to regulate which operations the API token can, or can not, carry out.




Headless CMS permissions

For our use case, right here is how we are going to set the controls.




Setting read & manage API controls




Setting content models API controls

Webiny Headless CMS consists of completely different API sorts, every of which has a separate endpoint and completely different objective. You possibly can learn extra about it right here. Above, below the GRAPHQL API TYPES heading we’ve got enabled the Learn and Handle sort to offer the API token to be generated permissions to make use of the endpoint of each sorts. We’re doing this in order that whereas sending requests to learn information we are able to use the Learn endpoint and for creating, enhancing and publishing we are able to use the Handle endpoint.

Above, below the PUBLISHING ACTIONS heading, we’ve got additionally enabled Publish motion. We’re doing this in order that utilizing the API token we can publish created entries since when entries are created they’re tagged as Draft and cannot be fetched with the Learn API endpoint until they’re revealed.

After setting the controls, click on on the SAVE API KEY button on the backside of the web page and our API token shall be created.




Completed API token

Now we are able to copy the token and retailer it in a .env file in our React app.

  • NOTE Since we’ve got enabled the Handle API sort for this token, it shouldn’t be used on the frontend. It’s positive when testing domestically in your machine, however when internet hosting your utility it’s greatest to make use of a server-side operate for sending requests as this instance code could be unsafe to make use of.

Subsequent, let’s get the endpoint for the Handle API and Learn API. Go to the API playground within the Admin app, click on on the Headless CMS – Handle API tab on the high of the web page copy the URL straight beneath the tab and provide it to the suitable variable within the .env file.




Where to find the GraphQL URL

Then, for the Learn endpoint, click on on the Headless CMS – READ API tab, copy the URL straight beneath the tab and provide it to the suitable variable within the .env file.




read api endpoint

Now, to initialize Apollo consumer, modify index.js file within the src listing to appear to be this:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/consumer';
import './index.css';
import App from './App';
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink, ApolloLink } from '@apollo/consumer';

const manageEndpoint = new HttpLink({
uri: course of.env.REACT_APP_WEBINY_GRAPHQL_MANAGE_URL,
headers : {
Authorization : `Bearer ${course of.env.REACT_APP_WEBINY_GRAPHQL_TOKEN}`
}
});

const readEndpoint = new HttpLink({
uri: course of.env.REACT_APP_WEBINY_GRAPHQL_READ_URL,
headers : {
Authorization : `Bearer ${course of.env.REACT_APP_WEBINY_GRAPHQL_TOKEN}`
}
});

const consumer = new ApolloClient({
hyperlink: ApolloLink.break up(
operation => operation.getContext().endpointType === "handle",
manageEndpoint,
readEndpoint,
),
cache: new InMemoryCache(),
});

const root = ReactDOM.createRoot(doc.getElementById('root'));
root.render(
<ApolloProvider consumer={consumer}>
<App />
</ApolloProvider>
)

Above, we’ve got initialized Apollo consumer to have the ability to interchangeably use the Learn and Handle endpoint utilizing the break up methodology in ApolloLink. This methodology receives three arguments. The primary is a check to examine the worth of endpointType equipped whereas sending requests. If the check passes (i.e when endpointType === "handle") the second argument which is the Handle endpoint shall be equipped because the hyperlink for the GraphQL request, but when it fails the third argument which is the Learn endpoint shall be equipped because the hyperlink.

Posting New Jobs

For this, we shall be utilizing createJob and publishJob Webiny GraphQL mutation features which had been robotically created for us after creating our content material mannequin. As their identify implies createJob is for creating a brand new job whereas publishJob is to publish a created job in order that it may be accessed by the Learn API endpoint. We may also be utilizing the useMutation hook from Apollo consumer for sending mutation requests to our GraphQL server.

Head over to src/PostJob.js and add the next import and mutations earlier than the PostJob element:

// src/PostJob.js
import { gql, useMutation } from '@apollo/consumer';

const POST_JOB = gql`
mutation CreateJob(
$title: String
$description: String
$sort: String
$station: String
$stage: String
){
createJob(information: {
title: $title
description: $description
sort: $sort
station: $station
stage: $stage
}) {
information {
id
}
}
}`

const PUBLISH_JOB = gql`
mutation PublishJob($revision: ID!) {
publishJob(revision: $revision) {
information {
id
}
}
}
`

Above, we’ve got created a POST_JOB mutation which calls createJob, and PUBLISH_JOB which calls publishJob. In them, we’ve got outlined the variables to be handed when they’re been known as.

Subsequent, add the next strains of code after the openSelect state:

// src/PostJob.js
const [postJob, { loading }] = useMutation(POST_JOB, {
context: {endpointType: 'handle'}
});

const [publishJob] = useMutation(PUBLISH_JOB, {
context: {endpointType: 'handle'}
})

const handleSubmit = (e) => {
e.preventDefault();
postJob({ variables: {
title,
description,
sort: jobType,
station: jobStation,
stage: jobLevel
}}).then(({information}) => {
publishJob({
variables: {
revision: information.createJob.information.id
}
})
closeModal()
})
}

Above, we’ve got known as two useMutaion hooks, for creating and publishing a job, passing the outlined mutations, specifying the endpoint for use, and destructuring the mutation execution operate postJob and publishJob respectively.

Now we have additionally created a handleSubmit operate which when known as will name postJob, passing the required variables. When the job is created and its ID is returned we name publishJob, cross the ID, after which shut the modal.

Now to name handleSubmit, modify the opening tag of the shape with identify='postJob' to the next:

// src/PostJob.js
<kind identify='postJob' className="postJob" onSubmit={handleSubmit} >

With this, once we open the app in our browser, click on the Submit Job button on the top-right of the web page, fill in and submit the displayed kind a job entry shall be added to our CMS. Subsequent, let’s show the created jobs within the UI.

Fetching and Displaying All Jobs

For fetching all jobs we shall be utilizing the listJobs Webiny question and useQuery hook from Apollo consumer to ship question requests to our GraphQL server.

Head over to src/App.js and add the next import and question:

// src/App.js
import { gql, useQuery } from '@apollo/consumer';

const LIST_JOBS = gql`
{
listJobs {
information {
id
title
description
sort
station
stage
}
}
}
`

Above, we’ve got created a LIST_JOBS which calls listJobs specifying the info we want to be returned from our CMS when the question is executed.

Subsequent, add the next line of code after the jobStation state:

// src/App.js
const {information} = useQuery(LIST_JOBS)

By calling the useQuery hook and passing within the LIST_JOBS question as above, every time our app renders will probably be executed and can return an object from Apollo Consumer that comprises the info from our CMS. The endpoint for use would be the READ endpoint as a result of the check completed within the break up methodology within the index.js file will fail since we didn’t specify handle because the endpoint sort.

Now to show the returned information, modify the next rendered element:

// src/App.js
<JobCard
title='JavaScript developer'
station='Distant'
sort='Full-time'
stage='Entery stage'
description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sollicitudin nulla turpis, ac convallis ligula euismod ut. Sed volutpat ac ligula a consectetur. Aenean ultrices finibus tellus, sit amet aliquet orci.'
/>

With this:

// src/App.js
{information?.listJobs?.information.map((job) => (
<JobCard
key={job.id}
id={job.id}
title={job.title}
station={job.station}
sort={job.sort}
stage={job.stage}
description={job.description}
/>
))}

With this, once we open our app within the browser we are going to see all of the posted jobs like this:




Complete list of jobs rendered from the API

Proper now if we submit a brand new job it gained’t be displayed until we refresh the web page. Let’s repair that.

There are just a few methods we are able to do that utilizing Apollo consumer however the very best is to incorporate a refetchQueries array within the mutation’s choices of the useMutation hook known as within the PostJob element passing the LIST_JOBS question to it one thing like this:

useMutation(POST_JOB, {
context: {endpointType: 'handle'},
refetchQueries: [
{query: LIST_JOBS}
],
});

Once we do that anytime we submit a job, the LIST_JOBS question shall be refreshed.

Within the starter app, I’ve included a prop for use in passing a question to the PostJob element, so now we have to cross the LIST_JOBS to that prop and use it within the useMutation hook.

Within the App element, modify the rendered Hero element including the next prop:

// src/App.js
<Hero
//...
queryToRefresh={LIST_JOBS}
/>

Subsequent, head over to src/PostJob.js and modify the useMutation hook to the next:

// src/PostJob.js
const [postJob, { loading }] = useMutation(POST_JOB, {
context: {endpointType: 'handle'},
refetchQueries: [
{query: queryToRefresh}
],
});

That’s it. Downside solved.

Looking and Filtering Jobs

Let’s work on making the search bar and filter choices useful.

For the search performance, we shall be utilizing the filter and consists of JavaScript methodology to filter the roles whose title consists of the textual content entered within the search bar. Then we are going to set the filtered ends in a state that can management what’s displayed within the UI.

To do that, the very first thing we are going to do is create a state to carry and show the returned information from our CMS fairly than displaying it straight. This shall be completed to assist us implement to go looking performance.

Head over to src/App.js and add the next import:

import {useEffect} from 'react'

Subsequent, add the next line of code after the useQuery hook within the App element:

// src/App.js
const [filteredJobs, setFilteredJobs] = useState([])

useEffect(() => {
if(information){
setFilteredJobs(information.listJobs.information)
}
}, [data])

Above we’ve got created a state and used the useEffect hook to set the returned information from our CMS to it.

Subsequent, let’s add controls to show what’s being rendered utilizing this state. Modify the div with className='jobs wrapper’ to the next:

// src/App.js
<div className='jobs wrapper'>
{filteredJobs.map((job) => (
<JobCard
key={job.id}
id={job.id}
title={job.title}
station={job.station}
sort={job.sort}
stage={job.stage}
description={job.description}
/>
))}
</div>

Subsequent, let’s create the operate for looking out. Add the next strains of code after the filteredJobs state:

// src/App.js
const handleSearch = (e) => {
e.preventDefault();
const filtered = information.listJobs.information.filter((job) =>
job.title.toLowerCase().consists of(searchValue.toLowerCase())
)
setFilteredJobs(filtered)
}

The above operate will set the filteredJobs state with the roles whose title consists of the textual content entered within the search bar. Now, we have to name this operate every time the Search button is clicked which is within the Search element in src/Search.js. This implies we’ve got to cross the handleSearch operate as prop all the way in which all the way down to the Search element.

I’ve already added a prop within the starter app tp for this. To make use of it modify the rendered Hero element to incorporate the next:

// src/App.js
<Hero
//...
onSearch={handleSearch}
/>

Now for the filter performance, what we need to do is that when a filter choice is clicked, all the roles that match the chosen choices needs to be displayed. For this we may also use the filter and consists of JavaScript methodology.

Add the next strains of code after the useEffect hook within the App element:

// src/App.js
useEffect(() => {
if(information) {
let filtered = information.listJobs.information
// for jobLevel choose
if(jobLevel.size) {
filtered = filtered.filter((job) =>
jobLevel.consists of(job.stage)
)
}
// for jobStation choose
if(jobStation.size) {
filtered = filtered.filter((job) =>
jobStation.consists of(job.station)
)
}
// for jobType choose
if(jobType.size) {
filtered = filtered.filter((job) =>
jobType.consists of(job.sort)
)
}
setFilteredJobs(filtered)
}
}, [jobType, jobStation, jobLevel, data])

Above we’ve got added a useEffect hook that runs anytime a filter choice is chosen. Within the useEffect, we create conditional statements for the array states holding the values of the chosen choices of a choose button in our app. Every conditional assertion will run solely when the state shouldn’t be empty is accountable for filtering the matching results of a specific choice and assigning it to the filtered variable which is later set within the state.

With this, we are able to begin filtering jobs in our app.

Making use of for a Job

When the Apply button on a job in our app is clicked we are going to see a kind for use for making use of to that job. Let’s work on making it useful. We may also work on displaying the entire variety of candidates for a job.

Keep in mind when creating the fields for the Software mannequin, we added a REFERENCE discipline to carry the ID of a job. That is key to implementing this function. By storing the ID of the job being utilized for together with the opposite fields for the job utility, we are able to filter all candidates to a selected job utilizing that ID.

To use for a job, we shall be utilizing the createApplication and publishApplication Webiny GraphQL mutation operate. Head over to src/Job.js and add the next import and mutation earlier than the Job element:

// src/Job.js
import { gql, useMutation } from '@apollo/consumer';

const APPLY_FOR_JOB = gql`
mutation CreateApplication(
$identify: String
$electronic mail: String
$web site: String
$linkedInProfile: String
$phoneNumber: Quantity
$ref: RefFieldInput
){
createApplication(information: {
identify: $identify
electronic mail: $electronic mail
web site: $web site
linkedInProfile: $linkedInProfile
phoneNumber: $phoneNumber
ref: $ref
}) {
information {
id
}
}
}`

const PUBLISH_APPLICATION = gql`
mutation PublishApplication($revision: ID!) {
publishApplication(revision: $revision) {
information {
id
}
}
}
`

Subsequent, add the next strains of code after the linkedInProfile state:

// src/Job.js
const [applyForJob] = useMutation(APPLY_FOR_JOB, {
context: {endpointType: 'handle'},
});
const [publishApplication] = useMutation(PUBLISH_APPLICATION, {
context: {endpointType: 'handle'},
})

const handleSubmit = (e) => {
e.preventDefault();
applyForJob({ variables: {
identify,
electronic mail,
web site,
linkedInProfile,
phoneNumber,
ref: {
modelId: "JOB",
id
}
}}).then(({information}) => {
publishApplication({
variables: {
revision: information.createApplication.information.id
}
})
closeModal()
})
}

Above, we’ve got created a handleSubmit operate which when known as will create a brand new entry for the Software mannequin and publish it.

Now to name handlSubmit, modify the opening tag of the shape with identify='applicationForm’ to the next:

// src/Job.js
<kind identify='applicationForm' onSubmit={handleSubmit}>

With this, we are able to now apply for a job once we open our app within the browser, click on on the Apply button on a job card, fill out the displayed kind and click on on the Submit Software button. Then for the applying to mirror within the UI we simply must refresh the web page.

Lastly, to show the variety of candidates for a selected job, we shall be fetching all of the functions in our CMS the place the reference ID is equal that job’s ID after which depend them. For this, we shall be utilizing the listApplications Webiny GraphQL question and it’s the place argument.

Head over to src/JobCard.js and add the next import and question earlier than the JobCard element:

// src/JobCard.js
import { gql, useQuery } from '@apollo/consumer';

const LIST_APPLICATION = gql`
question ListApplications($the place: ApplicationListWhereInput){
listApplications(the place: $the place) {
information {
id
}
}
}
`

Subsequent, add the next strains of code after the modalIsOpen state within the JobCard element:

// src/JobCard.js
const {information} = useQuery(LIST_APPLICATION, {
variables: {
the place: {ref: {id}}
}
})

const noOfApplicants = information?.listApplications?.information.size

With the above code, when our app renders, jobs having the required job ID shall be returned and the variety of candidates shall be saved within the noOfApplicants variable. To show it on the UI, modify the next span tag:

// src/JobCard.js
<span className='jobCard__applicants'>0 candidates</span>

With this:

// src/JobCard.js
<span className='jobCard__applicants'>
0 candidates
</span>

Additionally modify the rendered Job element by including the next prop:

<Job
// ...
noOfApplicants={noOfApplicants}
/>

With this, we’re completed constructing our job board.

Conclusion

On this tutorial, we’ve got discovered about Webiny Headless CMS and the way we use it in React by constructing a job board through which we included performance for posting jobs, looking out and filtering jobs and making use of for as job.

Full supply code: https://github.com/webiny/write-with-webiny/tree/major/tutorials/job-board-react


This text was written by a contributor to the Write with Webiny program. Would you want to put in writing a technical article like this and receives a commission to take action? Try the Write with Webiny GitHub repo.



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments