How to Create a Job Board in NextJS
Tags: javascript, webdev, react, nextjs
Published: 2022-03-31 22:01:03

Job boards are a convenient way to share job opportunities. With the advantage of being viewed from our mobile devices and PCs anytime globally, job boards have proven to be a better alternative to traditional means of sharing job opportunities.

## What we will be building

This post will discuss creating a simple job board that allows us to create, delete and display job opportunities in a Next.js project. We do not require a custom backend server.

**GitHub URL** 


https://github.com/Iheanacho-ai/appwrite-jobboard-next.js

## Prerequisites

To get the most out of this article, we require the following:

- A basic understanding of CSS, JavaScript, and React.js.
- Docker Desktop installed on the computer. Run the `docker -v` command to verify if we have docker desktop installed. If not, install it from [here](https://www.docker.com/products/docker-desktop/). 
- An Appwrite instance running on our computer. Check this [documentation](https://appwrite.io/?utm_source=hackmamba&utm_medium=blog&utm_campaign=nextjs-blog-bookmarks) to quickly learn how to create a local Appwrite instance. We will use Appwrite’s powerful database service and experience to manage our board.

## Setting up our Next.js app

[Next.js](https://nextjs.org/) is an open-source React framework that enables us to build server-side rendered static web applications.

To create our Next.js app, we navigate to our preferred directory and run the terminal command below: 

```bash
    npx create-next-app@latest
    # or
    yarn create next-app
```
After creating our app, we change the directory to our project and start a local development server with:

```bash
    cd <name of our project>
    npm run dev
```
To see our app, we go to [http://localhost:3000/](http://localhost:3000).

## Installing Appwrite

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=blog&utm_campaign=nextjs-blog-bookmarks) is an open-source, end-to-end, back-end server solution that allows developers to build applications faster. 

To use Appwrite in our Next.js application, we install the Appwrite client-side SDK by running this terminal command.

```bash
    npm install appwrite
```

## Creating a new Appwrite project

Running a local Appwrite instance gives us access to our console. We go to the local Appwrite instance on whatever port it is started on to create an account. Typically, this is on [localhost:80](http://localhost/) or as specified during Appwrite’s installation.

On the console, there is a **Create Project** button. Click on it to start a new project.

Our project dashboard appears once we have created the project. At the top of the page, there is a **settings** bar. Click it to access our **Project ID** and **API Endpoint**.  

We copy our **Project ID** and **API Endpoint**, which we need to initialize our Web SDK code.
In the root directory of our project, we create a `utils` folder, which will hold our `web-init.js` file. This file configures Appwrite in our application.

In our `utils/web-init.js` file, we initialize our Web SDK with:

```js
    // Init your Web SDK
    import { Appwrite } from "appwrite";
    const sdk = new Appwrite();
    sdk
        .setEndpoint('http://localhost/v1') // Your API Endpoint
        .setProject(projectID) // Your project ID
    ;
    
    export default sdk;
```

## Creating a collection and attributes

On the left side of our dashboard, we select the Database menu. We create a collection in our database tab by clicking on the **Add Collection** button. This action redirects us to a Permissions page.

At the **Collection Level**, we want our **Read Access** and **Write Access** to have a value of **role:all**.

![Appwrite Permission Page](https://paper-attachments.dropbox.com/s_3246DA27AE45A86F6F25AA1081C5093568E7040F843BFEDFC885B2DBE73C8346_1648395867802_Screenshot+67.png)


On the right of our **Permissions** page, we copy our collection ID, which we need to perform operations on documents in this collection.

Next, we go to our attributes tab to create the fields we want a document to have. These properties are **jobTitle**, **companyName**, **place**.


![Appwrite Database Dashboard](https://paper-attachments.dropbox.com/s_3246DA27AE45A86F6F25AA1081C5093568E7040F843BFEDFC885B2DBE73C8346_1648395984929_Screenshot+69.png)

## Creating the job entry page 

Our job board application will have two routes. One route will lead to a job entry page and another to a job listing page.

**Creating our Job Entry User Interface**

We will make our job entry page with a form. In our `index.js` file, we create this form with the code block below.
 
```jsx
    import { Appwrite } from 'appwrite';
    
    const Home = () => {
      return(
        <div className="create-job">
        <h2>Create a Job Post</h2>
        <form action="">
            <div className='txt-field'>
                <input type="text"/>
                <span></span>
                <label htmlFor="input">Job Title</label>
            </div>
            <div className='txt-field'>
                <input type="text" />
                <span></span>
                <label htmlFor="input">Company Name</label>
            </div>
            <div className='txt-field'>
                <input type="text"/>
                <span></span>
                <label htmlFor="input">Place</label>
            </div>
            <button type= "button" className='submit'>Add Job</button>
        </form>
        <p>See your Job Board <Link href="/list-job"><a>here</a></Link></p>
    </div>
      )
    }
  ```  

Next, we add our form styles.




https://gist.github.com/Iheanacho-ai/65a6ff9f2f372b2be2763482fc0f61bb


Here is our job entry page.


![Job Board Index page](https://paper-attachments.dropbox.com/s_3246DA27AE45A86F6F25AA1081C5093568E7040F843BFEDFC885B2DBE73C8346_1647775558089_Screenshot+18.png)


## Making our Job Entry Page interact with our Database.

**Creating an anonymous user session**

Appwrite requires a user to sign in before reading or writing to a database to enable safety in our application. However, they allow us to create an anonymous session that we’ll use in this project.

In our `index.js` file, we import `sdk` from our `web-init.js` file.

```jsx
    import sdk from '../utils/web-init';

```
Next, we create an anonymous user session once our app is mounted.

```js

    async function createAnonymousSession(){
        try{
            await sdk.account.createAnonymousSession();
        }catch(err){
            console.log(err)
        }
        
    }
    useEffect(()=> {
        createAnonymousSession()
    }, [])

```

**Creating state variables to hold our form values**

In our `index.js` file, we create state variables to hold the form input values. 

```js

    const [job, setJobTitle] = useState('')
    const [companyName, setCompanyName] = useState('')
    const [place, setPlace] = useState('')
```

In the `ìndex.js` file, we pass the state variables as the input field values. We then use the `onChange` event listener to update the state variable values when users type in the input fields.

```jsx

    <div className="create-job">
        <h2>Create a Job Post</h2>
        <form action="">
            <div className='txt-field'>
                <input type="text" value={job} onChange = {(e) => setJobTitle(e.target.value)}/>
                <span></span>
                <label htmlFor="input">Job Title</label>
            </div>
            <div className='txt-field'>
                <input type="text" value={companyName} onChange = {(e) => setCompanyName(e.target.value)}/>
                <span></span>
                <label htmlFor="input">Company Name</label>
            </div>
            <div className='txt-field'>
                <input type="text" value={place} onChange = {(e) => setPlace(e.target.value)}/>
                <span></span>
                <label htmlFor="input">Place</label>
            </div>
            <button type= "button" className='submit'>Add Job</button>
        </form>
        <p>See your Job Board <Link href="/list-job"><a>here</a></Link></p>
    </div>

```

**Creating database documents.**

In our `index.js` file, we write a `handleJobBoard` function to create documents in our collection.

```jsx
    const handleJobBoard = () => {
       let promise = sdk.database.createDocument(collectionID, 'unique()', {
         "jobTitle" : job,
         "companyName": companyName,
         "place": place
      });
      
      promise.then(function (response) {
          setJobTitle('');
          setCompanyName('');
          setPlace('');
          
          alert('your job item has been successfully saved'); // Success
      }, function (error) {
          console.log(error)
      });
    }

```
This `handleJobBoard` function above does the following :

- Uses the Appwrite `createDocument()` method, which creates a document using the collection ID and data fields to be stored.  This collection ID is the same ID we copied from our Permissions Page earlier.
- Alerts us when we have successfully saved our document, then clears the information in our local state variables.

Next, we pass our `handleJobBoard()` function into an `onClick` event listener on our `button` element.

```jsx
    <button type= "button" className='submit' onClick={handleJobBoard}>Add Job</button>

```

**NOTE: We must use a button with a `type= button` to override the button’s default submit behavior.**

Fill out the form and go to the Documents tab on the Appwrite’s project dashboard to see the saved documents.

![Appwrite Dashboard](https://paper-attachments.dropbox.com/s_3246DA27AE45A86F6F25AA1081C5093568E7040F843BFEDFC885B2DBE73C8346_1648416982234_Screenshot+78.png)


Our `index.js` file should look like the code below when we completed this tutorial section.


https://gist.github.com/Iheanacho-ai/d5a13a74774e453a54fa1536d8ddc6bb


## Creating our Job Listing Page

**Listing documents**

In our `pages` folder, we create a `list-job.jsx` file. The `list-job.jsx` file is responsible for creating our job listing page.

In our `pages/list-job`, we write this code for listing out documents in our collection.

```jsx
    import sdk from '../utils/web-init';
    import { useEffect, useState } from "react";
    
    const ListJob = () => {
        
        const [jobList, setJobList] = useState()
    
        const listProducts = async () => {
         try {
              let response = await sdk.database.listDocuments(collectionID);
              setJobList(response.documents)
              
          } catch (error) {
              console.log(error)
          }
      }    
        useEffect(() => {
            listProducts()
        }, [])
    
        return(
            <div className="list-job">Hello World!</div>
        )
    };
    export default ListJob;

```

We create a `jobList` state variable in the code block above. This variable will hold the information in our documents. We then create a `listProducts` function to display our jobs. We use Appwrite's `listDocuments()` method to do this. 

To specify what collection we want to access, we pass a collection ID parameter to the `listDocuments()` method. Lastly, we updated our `jobList` state variable.

Our `useEffect()` hook runs the `listProducts` function. 

**Deleting documents**

In our `pages/list-job.jsx` we create a `handleDelete()` funtion to delete documents we do not want anymore in our collection.  

```jsx
    import sdk from '../utils/web-init';
    import { useEffect, useState } from "react";
    
    const ListJob = () => {
      const [jobList, setJobList] = useState()
      const listProducts = async () => {
       try {
          let response = await sdk.database.listDocuments(collectionID);
          setJobList(response.documents)
          
      } catch (error) {
            console.log(error)
        }
      }
    
    // deletes our job
    
      const handleDelete = async (documentid) => {
        try {
            await sdk.database.deleteDocument(collectionID, documentid);
            alert("item have been deleted successfully")
            listProducts()
            
        } catch (error) {
            console.log(error)
        }
     }   
        useEffect(() => {
            listProducts()
        }, [])
    
        return(
            <div className="list-job">Hello World!</div>
        )
     };
    export default ListJob;
    
 ```   

The `handleDelete` function above does the following:

- Finds a document using its collection ID, and the document ID gets passed into the function.
- Deletes that document using Appwrite `deleteDocument()` method.
- Alerts us if we deleted an item.
- Runs our `listProducts` function to display our updated job list.
- Logs an error if deleting the document fails.

Next, in our `pages/list-job.jsx` file, we use JavaScript's ternary operator to conditionally render our job listing page.

```jsx
    return(
        <div className="list-job">
            {
                jobList ? <div>Hello World</div> : null
            }
        </div>
    )

```

Here is what our `pages/list-job.jsx` file looks like.

```jsx
    import sdk from '../utils/web-init';
    import { useEffect, useState } from "react";
    import ListJobItem from '../components/list-job-item';
    
    const ListJob = () => {
        const [jobList, setJobList] = useState()
    
        const listProducts = async () => {
            let response = await sdk.database.listDocuments(collectionID);
            setJobList(response.documents)
        }
        
        useEffect(() => {
            listProducts()
        }, [])
        
        const handleDelete = async (documentid) => {
            await sdk.database.deleteDocument(collectionID, documentid);
            alert("item have been deleted successfully")
            listProducts()
        }
        
    
        return(    
            <div className="list-job">
                {
                    jobList ? <div>Hello World!</div> : null
                }
        </div>
        )
    };
    export default ListJob;
```

**Creating a job item template and looping through them**

In our root directory, we create a `components` folder. This folder will contain our `list-job-item.jsx` file.

In our `components/list-job-item.jsx` file we create the template for a job item.

```jsx
    const ListJobItem = () => {
        return (
            <div className="list-job-item">
                <div className="item">
                    <h3>jobTitle goes here</h3>
                    <p>companyName goes heere</p>
                    <p>place goes here</p>
                    <button type= "button" className="delete">delete</button>
                </div>
            </div> 
        )
    }
    export default ListJobItem;

```

Next, we import the `ListJobItem` component into the `ListJob` component in the `list-job.jsx` file.

Following that, we pass the jobs data and the delete method as props to the rendered `ListJobItem` component.

```jsx
    return(
        <div className="list-job">
            {
                jobList ? <ListJobItem  jobList= {jobList} handleDelete={handleDelete}/> : null
            }
        </div>
    )

```

In our `components/list-job-item`,  we update the document to loop through the jobs passed as props, then render each one.

```jsx
    const ListJobItem = ({jobList, handleDelete}) => {
        return (
            <div className="list-job-item">
                {
                    jobList.map(({jobTitle, companyName, place, $id}) => (
                        <div className="item" id={$id}>
                            <h3>{jobTitle}</h3>
                            <p>{companyName}</p>
                            <p>{place}</p>
                            <button type= "button" className="delete" onClick={() => handleDelete($id)}>delete</button>
                        </div>
                    ))
                }
                
            </div> 
        )
    }
    export default ListJobItem;
```

In the code block above, we do the following: 

- Destructured our props and loop through the `jobList` variable using the JavaScript `map()` method.
- Destructured our `jobList` variable to get the `jobTitle`, `companyName`, `place`, and `$id`.
- Pass in our `$id` in the `handleDelete()` method, on the `onClick` event listener.

Our job listing page is incomplete without the styling. We add these styles in our `global.css` file.
 

https://gist.github.com/Iheanacho-ai/81b6adb59a902af2767ced7f7174b4d0

Fill out the form to see how our job board looks.


![Appwrite Job board](https://paper-attachments.dropbox.com/s_3246DA27AE45A86F6F25AA1081C5093568E7040F843BFEDFC885B2DBE73C8346_1647807565499_Screenshot+19.png)


 

## Conclusion

This article discussed using the Appwrite to quickly create, retrieve and delete data on our database. With this, we created a job board in a Next.js application. The created job board lacks other fields, therefore, improve this project to make a full featured job board. 

## Resources 

Here are some resources that might be helpful:

- [Getting started with Appwrite for web](https://appwrite.io/?utm_source=hackmamba&utm_medium=blog&utm_campaign=nextjs-blog-bookmarks)
- [Database API](https://appwrite.io/docs/client/database?utm_source=hackmamba&utm_medium=blog&utm_campaign=next-product-catalog)
- [What is Appwrite and How to use it Efficiently
](https://dev.to/noviicee/what-is-appwrite-and-how-to-use-it-efficiently-3o1m)