Build a Timeline Tracker with Cloudinary, Xata, and Next.js
Tags: webdev, xata, nextjs, javascript
Published: 2022-11-25 21:43:50

A timeline visually represents the work required to finish our project. It displays the dates that each activity was completed so we can monitor our progress. On the other hand, it may also convey what is required to achieve deadlines, making it more straightforward to manage expectations and assign priorities to projects in the long term.

The post will teach us how to build a timeline tracker with [Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) and [Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) in a Next.js application.

Check out the live demo [here](https://timeline-tracker-with-cloudinary-xata-and-next-js.vercel.app/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog).

GitHub Repository

{% embed https://github.com/Olanetsoft/Timeline-Tracker-with-Cloudinary-Xata-and-NextJs %}


## What is Cloudinary?

[Cloudinary](https://cloudinary.com/documentation/image_video_and_file_upload#upload_options_overview) provides a secure and comprehensive API for uploading media files fast and efficiently from the server side, the browser, or a mobile application. We can upload media assets using Cloudinary's REST API or client libraries (SDKs) which makes integrating with websites and mobile apps more accessible.


## What is Xata?

[Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) is a serverless data platform that enables us to manage, scale, prevent downtime, cache, and maintain our database to improve our development workflow. Additionally, it offers a relational database, an effective search engine, and much more.


## Prerequisites

Before getting started with this tutorial, we should have the following:

- Basic understanding of ES6 Javascript features
- NodeJs installed in our PC
- Knowledge of React and React hooks
- Yarn or NPM package manager


## Project Setup and Installation

To proceed, let us clone the starter project in our preferred directory with the git command below:


    git clone https://github.com/Olanetsoft/Timeline-Tracker-with-Cloudinary-Xata-and-NextJs/tree/starter

Run the following command to install all dependencies using the `yarn` package manager.


    yarn && yarn dev

 Or Run the following command to install all dependencies using the `npm` package manager to start the project on [http://localhost:3000](http://localhost:3000/).
 

    npm install && npm run dev 

 
 We should have something similar to what we have below.


![Timeline Tracker](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669245529841_Screenshot+2022-11-24+at+00.18.35.png)

## Creating and Setting Up Xata

In this section, we will set up a [Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) profile by [signing up](https://app.xata.io/signin?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) for a new account or [log](http://app.xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) [in](http://app.xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog). We will get redirected to our dashboard after successful sign-up, as shown below.


![Idris Olubisi Xata workspace](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669245912011_Screenshot+2022-11-22+at+23.56.16.png)


Next, we will click `Add a database`, enter our preferred database name***, and*** *click* `create`. We will be redirected to the database page similar to what is shown below.

![Add database on Xata](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246203683_Screenshot+2022-11-24+at+00.28.14.png)


We will create a table called `users` to save all the records of users that signed up on our platform, as shown below.

![Create database table on Xata](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246644628_Screenshot+2022-11-24+at+00.30.38.png)

![Create Table](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246644591_Screenshot+2022-11-24+at+00.33.30.png)


Add the following columns to the `users` table.

| Column Name | Data Type | Unique |
| ----------- | --------- | ------ |
| firstName   | String    | [ ]    |
| lastName    | String    | [ ]    |
| email       | Email     | [x]    |
| password    | String    | [ ]    |

We will repeat the above process to create a new table called `timelines` with the following columns;

| Column Name | Data Type | Unique | Table       |
| ----------- | --------- | ------ | ----------- |
| title       | String    | [ ]    | -           |
| description | String    | [ ]    | -           |
| timeline    | String    | [ ]    | -           |
| image_url   | String    | [ ]    | -           |
| user        | Link      | [ ]    | users table |

Next, we will connect our app to the remote Xata database we created using the following command below.


    # Install the Xata CLI globally
    npm i -g @xata.io/cli

After installing the Xata CLI globally, we will use the following command to initiate the database instance locally.


    xata init --db https://Olubisi-Idris-Ayinde-s-workspace-s.us-east-1.xata.sh/db/timeline

To get the above code snippet for our database, go to the user's table and click `Get code snippet,` as shown below.


![Get code snippet on Xata.](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247742239_Screenshot+2022-11-24+at+00.52.18.png)

![Copy code snippet on the Xata dashboard](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247742297_Screenshot+2022-11-24+at+00.53.04.png)


after running the command to initialize the project; we will see a prompt asking us to select a few configurations. Let us choose the following.

![Xata cli configuration](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247966843_Screenshot+2022-11-24+at+00.59.09.png)


Our browser automatically opens up to set a name for the API key, as shown below. Feel free to choose your preferred name.

![Set API key for Xata](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248110574_Screenshot+2022-11-24+at+01.00.44.png)

![API Key](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248163313_Screenshot+2022-11-24+at+01.02.19.png)


On our terminal, we can accept other prompts to add `.gitignore` and `.env,` as shown below.

![Xata CLI](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248276329_Screenshot+2022-11-24+at+01.03.33.png)


In the root of our project, we will see an `env` file with our `XATA_API_KEY` and `XataClient` inside the directory `/src/xata.js.`

We have successfully configured Xata in our project.


## Configuring Cloudinary and DB to Upload Images

We will be using Cloudinary's upload media assets. Create a [free Cloudinary account](https://cloudinary.com/users/register/free?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) to obtain **Cloud Name**, **API Key**, and **API Secret**.


![Cloudinary vonfig](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248936724_Screenshot+2022-11-24+at+01.13.12.png)


Update the `.env` file in the root directory of our project.


    CLOUDINARY_CLOUD_NAME= '*********************'
    CLOUDINARY_API_KEY='****************************'
    CLOUDINARY_SECRET= "****************************"

Stop the application from running in the terminal and run yarn dev or npm run dev to restart the server.

## Implementing User Authentication

In this section, we will implement user authentication functionality to help users register and login on to our application to create a timeline.


## Registration Functionality

Inside the `pages/api/` directory, update the `register.js` file with the following code snippet.


    # pages/api/register.js
    
    import { getXataClient } from "../../src/xata";
    import { promisify } from "util";
    import bcrypt from "bcryptjs"; // bcrypt to hash user password
             
    // Hash password with bcrypt
    const hash = promisify(bcrypt.hash);
     
    // initialize XataClient function
    const xata = getXataClient();
     
    export default async function register(req, res) {
     // Get user data from request body
     const { firstName, lastName, email, password } = req.body;
     
     // Fetch user from database using email address as unique identifier if it exists
     const userExist = await xata.db.users.filter({ email }).getFirst();
     
     // If user exists, return error
     if (userExist) {
         res.status(400);
         throw new Error("User already exists"); // throw error if user with email already exists
     }
     
     // Create a new user in the database
     const user = await xata.db.users.create({
         firstName,
         lastName,
         email,
         password: await hash(password, 10),
      });
     
     res.json({ message: "Success" });
       if (!user) {
       res.status(400);
       throw new Error("Invalid user data");
      }
    }


In the code snippet above, we:


- Created a function called `register` with `req` and `res` as a parameter
- Extracted `firstName, lastName, email, and password` from the request body
- Check if a user already exists using their email address
- Hash the user's password using the bycrypt package
- Create a new user record in our database using the [Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) client `getXataClient` and return a `success`  message if the user doedoesn'tist

Next, please navigate the `register.js` file under the `/pages` directory and update it with the following code snippet.

{% embed https://gist.github.com/Olanetsoft/48d1da1ff3c7e1afe2226446f9b5a88a %}


[https://gist.github.com/Olanetsoft/48d1da1ff3c7e1afe2226446f9b5a88a](https://gist.github.com/Olanetsoft/48d1da1ff3c7e1afe2226446f9b5a88a)

In the code snippet above, the `handleSubmit` function sends the user data to the register API we created earlier, allowing us to create a new user in our database.

Let's head over to the `Navber.js` file in the `components/` directory and update it with the following code snippet.


    import Head from "next/head";
    import Image from "next/image";
    import Link from "next/link";
    import { useRouter } from "next/router";
    
    const Navbar = ({ isAuthenticated }) => {
      const router = useRouter();
    
      return (
        <div className="relative container mx-auto px-6 flex flex-col space-y-2">
          <div className="flex flex-col space-y-4 pb-10">
            <header className="flex flex-col items-center justify-center space-y-4">
              <h1 className="text-4xl font-bold text-center">
                <Link href="/" className="text-white-600 hover:text-gray-400">
                  Build a Timeline Tracker with Cloudinary, Xata and NextJs
                </Link>
              </h1>
              {isAuthenticated ? (
                <button
                  className="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                  type="button"
                  onClick={() => {
                    router.push("/upload");
                  }}
                >
                  Add New Timeline
                </button>
              ) : (
                <button
                  className="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                  type="button"
                  onClick={() => {
                    router.push("/register");
                  }}
                >
                  Register or Login to Create a Timeline
                </button>
              )}
            </header>
          </div>
        </div>
      );
    };
    
    export default Navbar;

 In the code snippet above, we added `isAuthenticated` props and a check to only display `Register or Login to Create a Timeline` when a user is not logged in.
 
Next, we can update the `index.js` under the `pages/` directory with the following code snippet to implement the `isAuthenticated` data and retrieve all the timeline records created in our database.


{% embed https://gist.github.com/Olanetsoft/0e1f2cae8ca73a031ae94dc99e4062a2 %}


[https://gist.github.com/Olanetsoft/0e1f2cae8ca73a031ae94dc99e4062a2](https://gist.github.com/Olanetsoft/0e1f2cae8ca73a031ae94dc99e4062a2)

Heading over to our browser, we should have something similar to what is shown below. In the following section, we will implement the Login functionality.

![Build a Timeline Tracker with Cloudinary,Xata and NextJs](https://paper-attachments.dropboxusercontent.com/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669321707826_Screenshot+2022-11-24+at+21.27.02.png)



## Login Functionality

Similar to registering users on our platform, we will validate and log them into our system after registration. Let's update `login.js` in the `pages/api` directory with the following code snippet.


{% embed https://gist.github.com/Olanetsoft/12df225182f163ee6358a8b458908134 %}


[https://gist.github.com/Olanetsoft/12df225182f163ee6358a8b458908134](https://gist.github.com/Olanetsoft/12df225182f163ee6358a8b458908134)

In the code snippet above, we:

- Created a function called `login` with `req` and `res` as a parameter
- Extracted `email and password` from the request body
- Check if a user already exists using their email address from the Xata database
- Compare the hashed user's `password` using the bycrypt package
- Save the user token and Id in cookies to be used later in the tutorial

Next, we will update the `login.js` file in the `pages/` directory.


{% embed https://gist.github.com/Olanetsoft/19788a926afb407cd710c382dabbb05b %}


[https://gist.github.com/Olanetsoft/19788a926afb407cd710c382dabbb05b](https://gist.github.com/Olanetsoft/19788a926afb407cd710c382dabbb05b)

## Implementing User Upload Functionality

In the previous steps, we successfully implemented user authentication; we will proceed in this section to implement the upload functionality to allow logged-in users to create timelines.

In the `upload.js` file under the `pages/api` directory, let’s update it with the following code snippet.


{% embed https://gist.github.com/Olanetsoft/3cbb351d8e2d22ffb644de652bf99002 %}


[https://gist.github.com/Olanetsoft/3cbb351d8e2d22ffb644de652bf99002](https://gist.github.com/Olanetsoft/3cbb351d8e2d22ffb644de652bf99002)

In the code snippet above, we implemented an API that allows users to create a new timeline, upload it to Cloudinary and save it into our [Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) database.

Next, we will update the `upload.js` file in the `pages/` directory to consume the `upload` API we just implemented with the following code snippet.


{% embed https://gist.github.com/Olanetsoft/ea55a33ffedceee903109003c7557fbb %}


[https://gist.github.com/Olanetsoft/ea55a33ffedceee903109003c7557fbb](https://gist.github.com/Olanetsoft/ea55a33ffedceee903109003c7557fbb)

Testing our application, we should have something similar to what is shown below.


{% embed https://www.loom.com/share/c154afad4b894ccdb088ce4cacf519d7 %}

## Conclusion

This post teaches us how to build a timeline tracker using Cloudinary, [Xata](https://xata.io/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog), and NextJs.


## Resources
- [Xata Documentation](https://xata.io/docs/overview?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Media upload on Cloudinary](https://cloudinary.com/documentation/image_video_and_file_upload?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)