Build an Income tracker with NextJS using Cloudinary and Xata
Tags: jamstack, xata, nextjs, cloudinary
Published: 2022-11-29 23:54:31

An income tracker allows you to keep track of the inflow of cash available each month - making it especially helpful to stay on budget. At its most basic level, tracking your income and expenses on a regular basis helps you stay up-to-date with your financial information. 

In this article, you'll build an app that tracks your income and saves and delete your data on the database. You'll be exploring the rich capabilities of Xata, a serverless database, and Cloudinary, a cloud-based image and video management service.

## Prerequisites
The following is required to follow along smoothly with the tutorial:

- Basic understanding of Next.js
- Understanding of how [Typescript](https://www.typescriptlang.org/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) works
- A [Xata account](https://xata.io/signin?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- A [Cloudinary account](https://cloudinary.com/users/register/free?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- Recent version of [Node](https://nodejs.org/en/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) & [npm](https://www.npmjs.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) or [yarn](https://yarnpkg.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) package manager installed on your PC


## Creating a Next.js application

To create the Next.js app, go to your terminal or command line. Using the  `cd` command, we navigate to the directory we want our app to be created.

```javascript
cd <directory-name>
```

Once inside the directory, we create a new project using the command:

```javascript
npx create-next-app

# or

yarn create next-app
```
Once that's finished, we navigate into that directory and start a hot-reloading development server for the project on http://localhost:3000 with:

```javascript
npm run dev
#or
yarn dev
```

## Installing Cloudinary

[Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) provides a robust solution to store, transform, optimize and deliver images and videos in software applications.

We’ll install the `cloudinary-react` package that exposes various media delivery and transformation functions using the command line.

```javascript
npm i cloudinary-react lodash
```

> Lodash is a dependency of the Cloudinary package.

## Setting up the Xata database

Create a new database on your Xata dashboard called `income-tracker`

![Xata-database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6sbuel79utyfzabzpox.png)

Click on the created database and add a table titled `incomes`. Next, add a `desc`  column of type String, `price` column of type Integer, and a `date` column of type Date to the table.

Your database should look like this:

![xata-database-schema](https://paper-attachments.dropboxusercontent.com/s_4824FE67598837A0BC429306980E407942ABEE36289E86ACB837CF605093866F_1668751688956_image.png)

## Setting up the Xata instance

To set up Xata, you'll need to install the CLI globally;

```javascript
npm install @xata.io/cli -g
```
Then, authorize Xata to log you in;

```javascript
xata auth login
```
Next, select `Create a new API key in browser` from the prompts in your terminal. This opens your browser, where you can type in any name you choose. Once successful, you will get a display page indicating that you are all set.

Now `cd` into the Next.js project created earlier and run `xata init` to initialize Xata in your project;

```javascript
cd <directory-name>
xata init
```

The command above initializes the project with some questions in the terminal. This is where you select the name of the database created earlier, then select `Generate Typescript code` and choose your output source as `util/xata.ts` which is where the Xata codegen will be generated.

## Creating the income tracker interface

You'll need to create a folder named `components` in your Nextjs project, which will include the `Header`,  `IncomeForm`, `IncomeList` and `IncomeItem` components.

Now, inside the `Header.tsx` file and paste the below code:

```javascript
import Image from 'next/image';
const Header = ({ totalIncome } : any) => {
  return (
    <header>
      <h1 className="text-3xl font-bold text-[#888]">Income Tracker</h1>
      <Image
        className=" object-cover rounded-xl"
        width={80}
        height={1}
        src="https://res.cloudinary.com/beswift/image/upload/v1647849821/samples/cloudinary-icon.png"
        alt="plant"
      />
      <div className="total-income">${totalIncome}</div>
    </header>
  );
}
export default Header
```
From the code above, the `totalIncome` props were passed to the header and also the Cloudinary logo.

Next, inside the `IncomeForm.tsx`, paste the below code:

```javascript
import React, { useRef, useState } from "react";
const IncomeForm = ({ income, setIncome }: any) => {
  const [values, setValues] = useState({
    desc: "",
    price: "",
    date: "",
  });
  const desc = useRef<HTMLInputElement | null>(null);
  const date = useRef(null);
  const price = useRef<HTMLInputElement | null>(null);
  const handleChange = (event: any) => {
    setValues({
      ...values,
      [event.target.name]: event.target.value,
    });
  };
  const AddIncome = (e: { preventDefault: () => void }) => {
    e.preventDefault();
    const { date, price, desc } = values;
    let d = date.split("-").map((e) => parseInt(e));
    let newD = new Date(d[0], d[1] - 1, d[2]);
    const obj = {
      desc: desc,
      price: price,
      date: newD.toISOString(),
    };
    fetch("/api/add-income-item", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(obj),
    })
      .then((res) => res.json())
      .then(() => {
        setIncome([...income, obj]);
        setValues({
          desc: "",
          price: "",
          date: "",
        });
      })
      .catch(() => alert("An error occured"));
  };
  return (
    <form className="income-form" onSubmit={AddIncome}>
      <div className="form-inner ">
        <input
          type="text"
          name="desc"
          id="desc"
          placeholder="Income description..."
          value={values.desc}
          onChange={handleChange}
        />
        <input
          className="text-xl"
          type="number"
          name="price"
          id="price"
          placeholder="Price"
          value={values.price}
          onChange={handleChange}
        />
        <input
          type="date"
          name="date"
          id="date"
          placeholder="Income date"
          value={values.date}
          onChange={handleChange}
        />
        <input className="bg-[#FFCE00]" type="submit" value="Add Income" />
      </div>
    </form>
  );
};
export default IncomeForm;
```
The code snippet above did the following:

- Import required dependencies and image collections
- Create state variables to manage selected **desc**, **price**, and **date** of the income
- A `handleChange` function to control form inputs
- A `AddIncome` function that passed the income **desc**, **price**, and **date** to the xata database 
- Rendered the income form element to the web page

Next, the `IncomeItem.tsx` will display the list of the incomes on the screen

```typescript
import React from "react";
function IncomeItem({ income, index, removeIncome } : any) {
  let date = new Date(income.date);
  let day = date.getDate();
  let month = date.getMonth() + 1;
  let year = date.getFullYear();
  const removeHandle = (i: any) => {
      fetch("/api/delete-income", {
        method: "DELETE",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({id: income.id}),
      })
        .then((res) => res.json())
        .then(() => {
    removeIncome(i);
        })
        .catch(() => alert("An error occured"));
  };
  return (
    <div className="income-item">
      <button className="remove-item" onClick={() => removeHandle(index)}>
        X
      </button>
      <div className="desc">{income.desc}</div>
      <div className="price">${income.price}</div>
      <div className="date">{day + "/" + month + "/" + year}</div>
    </div>
  );
}
export default IncomeItem;
```
The code snippet above did the following:

- Import required dependencies
- A `removeHandle` function to delete data from the database

 
 Next, create the `IncomeList.tsx` that list all the income like this:

```javascript
import React from "react";
import IncomeItem from "./IncomeItem";
function IncomeList({ income, setIncome } : any) {
  const removeIncome = (i: any) => {
    let temp = income.filter((v: any, index: number) => index != i);
    setIncome(temp);
  };
  const sortByDate = (a: { date: number; }, b: { date: number; }) => {
    return a.date - b.date;
  }
  return (
    <div className="income-list">
      {income.sort(sortByDate).map((value: any, index: any) => (
        <IncomeItem
          key={index}
          income={value}
          index={index}
          removeIncome={removeIncome}
        />
      ))}
    </div>
  );
}
export default IncomeList;
```
The code snippet above did the following:

- Import required dependencies
- A `removeIncome` function to remove incomes according to the index 
- Loop through the incomes and display

## Storing data in the database

Create a new file in the `api` folder and name it `add-income-item.ts`. Paste the code below in the file:

```javascript
import type { NextApiRequest, NextApiResponse } from 'next'
import { getXataClient } from "../../util/xata";
const xata = getXataClient();
export type Response = {
  success: boolean;
  message?: string;
};
const handler = async (
  req: NextApiRequest,
  res: NextApiResponse<Response>
) => {
  try {
  const { desc, price, date } = req.body;
  await xata.db.incomes.create({
    desc,
    price: parseFloat(price),
    date,
  });
  res.json({
    success: true,
  });
  } catch (error: any) {
    console.log(error)
  res.json({
    success: false,
    message: error?.message
  });
  }
};
export default handler;
```
You get the data sent to the API and save it to the Xata database.

## Deleting data in the database

Create another new file in the `api` folder and name it `delete-income.ts`. Paste the code below in the file:

```javascript
import type { NextApiRequest, NextApiResponse } from "next";
import { getXataClient } from "../../util/xata";
const xata = getXataClient();
export type Response = {
  success: boolean;
  message?: string;
};
const handler = async (req: NextApiRequest, res: NextApiResponse<Response>) => {
  try {
    const { id } = req.body;
    await xata.db.incomes.delete({
      id: id
    });
    res.json({
      success: true,
    });
  } catch (error: any) {
    console.log(error);
    res.json({
      success: false,
      message: error?.message,
    });
  }
};
export default handler;
```
You get to delete data from the Xata database through the API.

To see the app in action, run `npm run dev`, and visit the URL. You should see a screen like this. 


![income-tracker-application](https://videoapi-muybridge.vimeocdn.com/animated-thumbnails/image/c1f58b68-9f78-4d95-9dbe-6b9267396d15.gif?ClientID=vimeo-core-prod&Date=1668838186&Signature=330d91f829044abc29e6e31fad4a421ec0b0b159)

![income-tracker xata-database](https://paper-attachments.dropboxusercontent.com/s_4824FE67598837A0BC429306980E407942ABEE36289E86ACB837CF605093866F_1668838299566_image.png)


Using Jamstack technologies, you have successfully built an income tracker application. You can view the project demo [here](https://xata-income-tracker.vercel.app/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) and access the source code in this [GitHub repo](https://github.com/Tundesamson26/xata-income-tracker/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog).

## Conclusion

In this article, you created an income tracker app that helped you track and manage your income and expenses. You also explore using Xata for seamless database storage and Cloudinary for easy image uploads.

## Resources
- [Xata documentation](https://xata.io/docs/quickstart/index?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Cloudinary documentation](https://cloudinary.com/documentation?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Next.js documentation](https://nextjs.org/docs/getting-started?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)