Learn how to build a simple finance tracker with Replit. Follow this step-by-step guide to manage expenses and improve your coding skills efficiently

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A simple but real-world Finance Tracker on Replit can be built as a small full-stack project using Node.js (for backend), Express (for API routes), and SQLite (a lightweight file‑based database that works well on Replit). You’ll build a small web app with an input form for transactions (like expenses or income) and a table to display saved data. Replit hosts the app live, stores secrets like API keys in its Secrets tab, and you don’t need to deploy elsewhere unless you want a custom domain.
On Replit, create a new Repl using the Node.js template. This gives you an empty environment with a index.js entry point and a working console. It automatically sets up package.json for dependency management.
Then install dependencies in the shell:
npm install express better-sqlite3
Express handles web routes; better-sqlite3 is a stable, synchronous SQLite client for Node.js and works perfectly in Replit.
Inside the Repl filetree, create a new folder named db and inside it create a file database.js. This will initialize the SQLite database and define a “transactions” table.
// db/database.js
const Database = require('better-sqlite3');
const db = new Database('finance.db'); // creates or opens finance.db file
// Create table if not exists
db.prepare(`
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
description TEXT NOT NULL,
amount REAL NOT NULL,
date TEXT DEFAULT CURRENT_TIMESTAMP
)
`).run();
module.exports = db;
This script ensures that the first run creates finance.db file at the root directory of the Repl.
In your main index.js file (already present by default), set up a simple Express app. This will serve the static frontend and expose API routes to add or list transactions.
// index.js
const express = require('express');
const db = require('./db/database');
const app = express();
app.use(express.json());
app.use(express.static('public')); // serve files from public folder
// API route to add transaction
app.post('/api/transactions', (req, res) => {
const { description, amount } = req.body;
if (!description || !amount) {
return res.status(400).json({ error: 'Missing description or amount' });
}
const stmt = db.prepare('INSERT INTO transactions (description, amount) VALUES (?, ?)');
stmt.run(description, amount);
res.json({ success: true });
});
// API route to get all transactions
app.get('/api/transactions', (req, res) => {
const rows = db.prepare('SELECT * FROM transactions ORDER BY date DESC').all();
res.json(rows);
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server running on port ${port}`));
Every call to /api/transactions reads or writes to the database. Express serves the frontend from a folder called public.
Now make a new folder named public at the root of your Repl. Inside it, create three files: index.html, style.css, and script.js. These form your app’s basic frontend.
index.html (this is the user interface):
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Finance Tracker</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Finance Tracker</h1>
<form id="transactionForm">
<input type="text" id="description" placeholder="Description" required />
<input type="number" id="amount" step="0.01" placeholder="Amount" required />
<button type="submit">Add</button>
</form>
<table id="transactionsTable">
<thead>
<tr><th>Date</th><th>Description</th><th>Amount</th></tr>
</thead>
<tbody></tbody>
</table>
<script src="script.js"></script>
</body>
</html>
style.css (simple styling):
/* public/style.css */
body {
font-family: Arial, sans-serif;
padding: 20px;
}
h1 {
color: #333;
}
form {
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 8px;
border-bottom: 1px solid #ccc;
}
td {
text-align: center;
}
script.js (frontend logic):
// public/script.js
async function loadTransactions() {
const res = await fetch('/api/transactions');
const data = await res.json();
const tbody = document.querySelector('#transactionsTable tbody');
tbody.innerHTML = '';
data.forEach(tx => {
const row = `<tr>
<td>${new Date(tx.date).toLocaleString()}</td>
<td>${tx.description}</td>
<td>${tx.amount.toFixed(2)}</td>
</tr>`;
tbody.insertAdjacentHTML('beforeend', row);
});
}
document.querySelector('#transactionForm').addEventListener('submit', async (e) => {
e.preventDefault();
const description = document.querySelector('#description').value;
const amount = parseFloat(document.querySelector('#amount').value);
await fetch('/api/transactions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ description, amount })
});
e.target.reset();
loadTransactions(); // refresh table
});
loadTransactions();
Click the “Run” button in Replit. Replit builds and starts the server. You’ll see “Server running on port 3000” in the console. The webview (the right side of the editor) shows your finance tracker. Try adding a few transactions; the table updates instantly using the API you built.
Now you have a complete, working finance tracker fully hosted on Replit, using real backend, database, and frontend code. You can enhance it with categories, user authentication (with Replit Auth or JWT), or export reports later, but this foundation already teaches you the key patterns for a genuine production-like project on Replit.
<script type="module">
import express from 'express'
import bodyParser from 'body-parser'
import Database from '@replit/database'
const app = express()
const db = new Database()
app.use(bodyParser.json())
// Normalize categories and prevent duplicate entries for same date
function normalizeTransaction(tx) {
return {
date: new Date(tx.date).toISOString().split('T')[0],
amount: Number(tx.amount),
category: tx.category.trim().toLowerCase(),
note: tx.note?.trim() || '',
}
}
app.post('/api/transactions', async (req, res) => {
const tx = normalizeTransaction(req.body)
const existing = (await db.get(tx.date)) || []
const updated = [...existing, tx]
await db.set(tx.date, updated)
res.json({ success: true, transactions: updated })
})
app.get('/api/summary/:month', async (req, res) => {
const data = await db.list()
const entries = Object.entries(data)
const month = req.params.month
const summary = entries.reduce((acc, [date, txs]) => {
if (date.startsWith(month)) {
txs.forEach(tx => {
acc[tx.category] = (acc[tx.category] || 0) + tx.amount
})
}
return acc
}, {})
res.json(summary)
})
app.listen(3000, () => console.log('Finance tracker API running on port 3000'))
</script>
<script type="module">
import express from 'express'
import fetch from 'node-fetch'
const app = express()
app.use(express.json())
// Endpoint to fetch live exchange rate and convert USD to target currency
app.get('/api/convert', async (req, res) => {
const { amount, to } = req.query
if (!amount || !to) return res.status(400).json({ error: 'Missing parameters' })
try {
const response = await fetch(`https://api.exchangerate.host/latest?base=USD&symbols=${to.toUpperCase()}`)
const data = await response.json()
const rate = data.rates[to.toUpperCase()]
if (!rate) return res.status(404).json({ error: 'Currency not found' })
const converted = Number(amount) \* rate
res.json({ from: 'USD', to, rate, original: Number(amount), converted })
} catch (err) {
console.error('Conversion error:', err)
res.status(500).json({ error: 'Failed to fetch exchange rate' })
}
})
app.listen(3000, () => console.log('Currency conversion service running on port 3000'))
</script>
<script type="module">
import express from 'express'
import Database from '@replit/database'
import bcrypt from 'bcrypt'
const app = express()
const db = new Database()
app.use(express.json())
// Secure user signup + login with hashed password in Replit DB
app.post('/api/signup', async (req, res) => {
const { username, password } = req.body
if (!username || !password) return res.status(400).json({ error: 'Missing fields' })
const existing = await db.get(`user:${username}`)
if (existing) return res.status(400).json({ error: 'User already exists' })
const hash = await bcrypt.hash(password, 10)
await db.set(`user:${username}`, { username, hash })
res.json({ success: true })
})
app.post('/api/login', async (req, res) => {
const { username, password } = req.body
const user = await db.get(`user:${username}`)
if (!user) return res.status(401).json({ error: 'Invalid credentials' })
const valid = await bcrypt.compare(password, user.hash)
if (!valid) return res.status(401).json({ error: 'Invalid credentials' })
// Example: store session in DB (temporary / simplified)
const sessionToken = crypto.randomUUID()
await db.set(`session:${sessionToken}`, username)
res.json({ success: true, token: sessionToken })
})
app.listen(3000, () => console.log('Auth service ready on port 3000'))
</script>

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
When building a Finance Tracker on Replit, treat it like a real full‑stack web app — keep backend, frontend, and secrets organized. Use Replit’s built‑in Node.js or Python templates, store sensitive data in Secrets (never in code), and use a small hosted database like Replit DB or an external database. Always remember that Replit’s file system resets on some deployments, so persist all data outside local files. Use the Replit shell for installing dependencies, the built‑in Git integration for version control, and the “Always On” or “Deployments” features to keep your tracker running reliably.
Create a new Repl with the Node.js template (good for combining backend and frontend easily). After it opens, your main files will be:
// Inside the Replit shell, install Express (backend server) and Replit DB
npm install express @replit/database
Your backend handles routes (like saving or fetching expenses). Add the code below inside index.js. This creates a simple Express server connected to Replit DB.
// Import required packages
const express = require("express");
const Database = require("@replit/database");
// Initialize
const app = express();
const db = new Database();
app.use(express.json()); // Parse incoming JSON data
// Route: add a new expense
app.post("/add-expense", async (req, res) => {
const { description, amount } = req.body;
const id = Date.now(); // unique key
await db.set(id, { description, amount });
res.json({ success: true });
});
// Route: get all expenses
app.get("/expenses", async (req, res) => {
const keys = await db.list();
const expenses = [];
for (const key of keys) {
const item = await db.get(key);
expenses.push({ id: key, ...item });
}
res.json(expenses);
});
// Starting the server
app.listen(3000, () => console.log("Server running on port 3000"));
This backend script stays in the root index.js file. Replit automatically runs it when you press the “Run” button. When it says “Server running,” the mini webserver is active.
Create a public/index.html file that allows a user to add and view expenses. Include simple forms that send requests to the backend routes we defined before.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Finance Tracker</title>
</head>
<body>
<h1>My Finance Tracker</h1>
<form id="expense-form">
<input type="text" id="desc" placeholder="Description" required />
<input type="number" id="amount" placeholder="Amount" required />
<button type="submit">Add Expense</button>
</form>
<h3>Expenses</h3>
<ul id="expense-list"></ul>
<script>
// Load expenses from server
async function loadExpenses() {
const res = await fetch("/expenses");
const data = await res.json();
const list = document.getElementById("expense-list");
list.innerHTML = "";
data.forEach(e => {
const li = document.createElement("li");
li.textContent = `${e.description}: $${e.amount}`;
list.appendChild(li);
});
}
// Handle form submit
document.getElementById("expense-form").addEventListener("submit", async (ev) => {
ev.preventDefault();
const description = document.getElementById("desc").value;
const amount = parseFloat(document.getElementById("amount").value);
await fetch("/add-expense", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ description, amount })
});
loadExpenses();
});
loadExpenses();
</script>
</body>
</html>
To serve the frontend, modify your index.js file to include a static route right before your routes:
app.use(express.static("public"));
This tells Express to serve your HTML and JavaScript files from the public folder. After saving, click “Run.” Your Replit URL will now show the finance tracker’s web page.
If later you connect to an external API or database (like MongoDB), do not hardcode credentials. In Replit, click the Secrets tab (lock icon on the left) and add them there. Access them in your code using:
const mongoURI = process.env.MONGO_URI;
Following this approach gives you a real, working finance tracker app fully hosted and runnable on Replit, with correct file structure, persistent data, and maintainable code separation.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.