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 works
- A Xata account
- A Cloudinary account
- Recent version of Node & npm or yarn 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.
cd <directory-name>
Once inside the directory, we create a new project using the command:
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:
npm run dev
#or
yarn dev
Installing Cloudinary
Cloudinary 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.
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
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:
Setting up the Xata instance
To set up Xata, you'll need to install the CLI globally;
npm install @xata.io/cli -g
Then, authorize Xata to log you in;
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;
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:
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:
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
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:
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:
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:
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.
Using Jamstack technologies, you have successfully built an income tracker application. You can view the project demo here and access the source code in this GitHub repo.
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.