Here’s a complete HTML, CSS, and JavaScript implementation for a mobile number tracking app similar to TrueCaller. This is a frontend-only implementation that simulates the functionality.
In today’s digital age, mobile number tracking apps like TrueCaller have become essential for identifying unknown callers, blocking spam, and maintaining privacy. If you’re looking to develop a TrueCaller-like mobile number tracking app, this blog will guide you through its key features, benefits, and development process while ensuring copyright-free qualitative content.
Why Develop a TrueCaller-Like App?
With increasing spam calls and fraud attempts, users demand reliable caller identification apps. A TrueCaller-like app helps:
- Identify unknown callers in real-time.
- Block spam and telemarketing calls.
- Enhance user privacy by providing caller details.
- Improve call management with smart features.
Key Features of a TrueCaller-Like App
1. Caller ID & Spam Detection
- Real-time number lookup from a crowdsourced database.
- Spam detection using AI to flag fraudulent numbers.
2. Call Blocking & Smart Dialer
- Manual/Auto-block spam numbers.
- Smart dialer integration for instant caller info.
3. User-Contributed Database
- Community-driven updates for accurate number tagging.
- Report spam to improve database reliability.
4. Reverse Phone Lookup
- Search unknown numbers to get name, location, and carrier details.
5. Privacy & Security
- GDPR & compliance-friendly data handling.
- Opt-in/Opt-out options for user privacy.
6. Multi-Platform Support
- Android & iOS compatibility.
- Web version for number lookups.
Complete Code
HTML(index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CallTrack - Mobile Number Tracker | CodeWithTanveer</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app-container">
<!-- Header -->
<div class="header">
<div class="logo">CallTrack</div>
<div class="menu-icon" id="menuBtn">☰</div>
</div>
<!-- Search Section -->
<div class="search-section">
<div class="search-container">
<input
type="text"
class="search-input"
id="searchInput"
placeholder="Enter phone number"
maxlength="15"
value="+91 "
/>
<button class="search-btn" id="searchBtn">Search</button>
</div>
</div>
<!-- Spinner -->
<div class="spinner" id="spinner"></div>
<!-- No Results -->
<div class="no-results" id="noResults">
<div style="font-size: 50px; margin-bottom: 10px">🔍</div>
<h3>No results found</h3>
<p>Try searching for a different number</p>
</div>
<!-- Result Section -->
<div class="result-section" id="resultSection">
<div class="result-card">
<div class="number-display" id="resultNumber">+91 8907659090</div>
<div class="name-display" id="resultName">John Doe</div>
<div class="location-display" id="resultLocation">New York, USA</div>
<div class="spam-score spam-low" id="resultSpam">Low spam risk</div>
<div class="carrier-info">
<div class="carrier-item">
<div class="carrier-label">Carrier</div>
<div class="carrier-value" id="resultCarrier">AT&T</div>
</div>
<div class="carrier-item">
<div class="carrier-label">Type</div>
<div class="carrier-value" id="resultType">Mobile</div>
</div>
<div class="carrier-item">
<div class="carrier-label">Reports</div>
<div class="carrier-value" id="resultReports">12</div>
</div>
</div>
</div>
</div>
<!-- Recent Searches -->
<div class="recent-section" id="recentSection">
<div class="section-title">
Recent Searches
<span class="clear-all" id="clearAll">Clear all</span>
</div>
<ul class="recent-list" id="recentList">
<!-- Recent items will be added here dynamically -->
</ul>
</div>
<!-- Bottom Navigation -->
<div class="bottom-nav">
<div class="nav-item active">
<div class="nav-icon">🔍</div>
<div class="nav-label">Search</div>
</div>
<div class="nav-item">
<div class="nav-icon">📞</div>
<div class="nav-label">Calls</div>
</div>
<div class="nav-item">
<div class="nav-icon">👤</div>
<div class="nav-label">Profile</div>
</div>
<div class="nav-item">
<div class="nav-icon">⚙️</div>
<div class="nav-label">Settings</div>
</div>
</div>
</div>
<!-- Side Menu -->
<div class="side-menu" id="sideMenu">
<div class="menu-header">
<div class="user-avatar">JD</div>
<div>
<div class="user-name">John Doe</div>
<div class="user-email">john.doe@example.com</div>
</div>
</div>
<div class="close-menu" id="closeMenu">×</div>
<ul class="menu-items">
<li class="menu-item">
<div class="menu-icon">👤</div>
<div class="menu-text">My Profile</div>
</li>
<li class="menu-item">
<div class="menu-icon">🔔</div>
<div class="menu-text">Notifications</div>
</li>
<li class="menu-item">
<div class="menu-icon">📞</div>
<div class="menu-text">Call History</div>
</li>
<li class="menu-item">
<div class="menu-icon">🛡️</div>
<div class="menu-text">Blocked Numbers</div>
</li>
<li class="menu-item">
<div class="menu-icon">⚙️</div>
<div class="menu-text">Settings</div>
</li>
<li class="menu-item">
<div class="menu-icon">❓</div>
<div class="menu-text">Help & Feedback</div>
</li>
<li class="menu-item">
<div class="menu-icon">🚪</div>
<div class="menu-text">Logout</div>
</li>
</ul>
</div>
<!-- Overlay -->
<div class="overlay" id="overlay"></div>
<script src="script.js"></script>
</body>
</html>
CSS(style.css)
/* CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
/* Variables */
:root {
--primary-color: #2e86de;
--secondary-color: #54a0ff;
--danger-color: #ff6b6b;
--success-color: #1dd1a1;
--dark-color: #2f3640;
--light-color: #f5f6fa;
--gray-color: #a4b0be;
}
body {
background-color: var(--light-color);
color: var(--dark-color);
}
/* App Container */
.app-container {
max-width: 500px;
margin: 0 auto;
background-color: white;
min-height: 100vh;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
position: relative;
overflow-x: hidden;
}
/* Header */
.header {
background-color: var(--primary-color);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
}
.logo {
font-size: 22px;
font-weight: bold;
}
.menu-icon {
font-size: 20px;
cursor: pointer;
}
/* Search Section */
.search-section {
padding: 20px;
background-color: var(--primary-color);
position: relative;
}
.search-container {
display: flex;
background-color: white;
border-radius: 30px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.search-input {
flex: 1;
padding: 12px 15px;
border: none;
outline: none;
font-size: 16px;
}
.search-btn {
background-color: var(--secondary-color);
color: white;
border: none;
padding: 0 20px;
cursor: pointer;
font-size: 16px;
}
/* Result Section */
.result-section {
padding: 20px;
display: none;
}
.result-card {
background-color: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
text-align: center;
}
.number-display {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
color: var(--primary-color);
}
.name-display {
font-size: 20px;
margin-bottom: 5px;
}
.location-display {
color: var(--gray-color);
margin-bottom: 15px;
}
.spam-score {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
margin-bottom: 15px;
}
.spam-low {
background-color: rgba(29, 209, 161, 0.2);
color: var(--success-color);
}
.spam-medium {
background-color: rgba(255, 165, 0, 0.2);
color: orange;
}
.spam-high {
background-color: rgba(255, 107, 107, 0.2);
color: var(--danger-color);
}
.carrier-info {
display: flex;
justify-content: space-between;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.carrier-item {
text-align: center;
flex: 1;
}
.carrier-label {
font-size: 12px;
color: var(--gray-color);
}
.carrier-value {
font-weight: bold;
margin-top: 5px;
}
/* Recent Searches */
.recent-section {
padding: 20px;
}
.section-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.clear-all {
color: var(--primary-color);
font-size: 14px;
cursor: pointer;
}
.recent-list {
list-style: none;
}
.recent-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.recent-item:last-child {
border-bottom: none;
}
.recent-number {
font-weight: bold;
}
.recent-name {
color: var(--gray-color);
font-size: 14px;
}
.recent-spam {
width: 10px;
height: 10px;
border-radius: 50%;
}
.spam-indicator-low {
background-color: var(--success-color);
}
.spam-indicator-medium {
background-color: orange;
}
.spam-indicator-high {
background-color: var(--danger-color);
}
/* Bottom Navigation */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
max-width: 500px;
margin: 0 auto;
display: flex;
justify-content: space-around;
background-color: white;
padding: 10px 0;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
}
.nav-item {
text-align: center;
color: var(--gray-color);
cursor: pointer;
}
.nav-item.active {
color: var(--primary-color);
}
.nav-icon {
font-size: 20px;
margin-bottom: 5px;
}
.nav-label {
font-size: 12px;
}
/* Loading Spinner */
.spinner {
display: none;
width: 40px;
height: 40px;
margin: 20px auto;
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top-color: var(--primary-color);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* No Results */
.no-results {
text-align: center;
padding: 40px 20px;
color: var(--gray-color);
display: none;
}
/* Side Menu */
.side-menu {
position: fixed;
top: 0;
left: -300px;
width: 300px;
height: 100vh;
background-color: white;
z-index: 200;
transition: left 0.3s ease;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
}
.side-menu.active {
left: 0;
}
.menu-header {
background-color: var(--primary-color);
color: white;
padding: 20px;
display: flex;
align-items: center;
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: white;
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
margin-right: 15px;
}
.user-name {
font-weight: bold;
margin-bottom: 5px;
}
.user-email {
font-size: 12px;
opacity: 0.8;
}
.close-menu {
position: absolute;
right: 15px;
top: 15px;
font-size: 20px;
cursor: pointer;
}
.menu-items {
list-style: none;
padding: 20px 0;
}
.menu-item {
padding: 15px 20px;
display: flex;
align-items: center;
cursor: pointer;
}
.menu-item:hover {
background-color: #f5f6fa;
}
.menu-icon {
margin-right: 15px;
color: var(--gray-color);
}
.menu-text {
font-size: 16px;
}
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 150;
display: none;
}
.overlay.active {
display: block;
}
/* Responsive */
@media (max-width: 500px) {
.app-container {
box-shadow: none;
}
}
JavaScript(script.js)
// DOM Elements
const searchInput = document.getElementById("searchInput");
const searchBtn = document.getElementById("searchBtn");
const resultSection = document.getElementById("resultSection");
const recentSection = document.getElementById("recentSection");
const recentList = document.getElementById("recentList");
const clearAll = document.getElementById("clearAll");
const spinner = document.getElementById("spinner");
const noResults = document.getElementById("noResults");
const menuBtn = document.getElementById("menuBtn");
const sideMenu = document.getElementById("sideMenu");
const closeMenu = document.getElementById("closeMenu");
const overlay = document.getElementById("overlay");
// Result elements
const resultNumber = document.getElementById("resultNumber");
const resultName = document.getElementById("resultName");
const resultLocation = document.getElementById("resultLocation");
const resultSpam = document.getElementById("resultSpam");
const resultCarrier = document.getElementById("resultCarrier");
const resultType = document.getElementById("resultType");
const resultReports = document.getElementById("resultReports");
// API Configuration
const API_KEY = "YOUR_API_KEY";
const API_URL = "https://apilayer.net/api/validate";
// Recent searches from localStorage
let recentSearches = JSON.parse(localStorage.getItem("recentSearches")) || [];
// Search function with country code support
async function searchNumber() {
const searchValue = searchInput.value.replace(/\D/g, "");
if (searchValue.length < 7) {
alert("Please enter a valid phone number (at least 7 digits)");
return;
}
// Show spinner
spinner.style.display = "block";
resultSection.style.display = "none";
noResults.style.display = "none";
recentSection.style.display = "none";
try {
let numberToSearch = searchValue;
let countryCode = "";
if (searchValue.length > 10) {
countryCode = searchValue.substring(0, searchValue.length - 10);
numberToSearch = searchValue.substring(searchValue.length - 10);
}
// Make API call with country code if available
let apiUrl = `${API_URL}?access_key=${API_KEY}&number=${numberToSearch}&format=1`;
if (countryCode) {
apiUrl += `&country_code=IN`;
}
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
// Hide spinner
spinner.style.display = "none";
if (data.valid) {
displayResultFromAPI(data);
addToRecentSearches(searchValue, data);
} else {
noResults.style.display = "block";
recentSection.style.display = "block";
// Store even invalid searches
addToRecentSearches(searchValue, {
carrier: "Unknown",
country_name: "Unknown",
location: "Unknown",
line_type: "Unknown",
});
}
} catch (error) {
console.error("API Error:", error);
spinner.style.display = "none";
// Fallback to mock data if API fails
const mockData = getMockData(searchValue);
if (mockData) {
displayResultFromAPI(mockData);
addToRecentSearches(searchValue, mockData);
} else {
noResults.style.display = "block";
recentSection.style.display = "block";
alert("Error fetching data. Using demo mode with limited functionality.");
}
}
}
// Display search result from API data
function displayResultFromAPI(data) {
// Format the number for display
const formattedNumber = data.international_format
? data.international_format
.replace("+", "+")
.replace(/(\d{2})(\d{3})(\d{3})(\d{4})/, "$1 $2 $3 $4")
: `+${data.number.substring(
0,
data.number.length - 10
)} ${data.number.substring(-10, 3)} ${data.number.substring(
3,
6
)} ${data.number.substring(6)}`;
resultNumber.textContent = formattedNumber;
resultName.textContent = data.carrier || "Unknown";
resultLocation.textContent = data.country_name
? `${data.country_name}${data.location ? ` (${data.location})` : ""}`
: "Location unknown";
resultCarrier.textContent = data.carrier || "Unknown";
resultType.textContent = data.line_type
? data.line_type.charAt(0).toUpperCase() + data.line_type.slice(1)
: "Unknown";
resultReports.textContent = "N/A";
// Set spam score
const spamScore = simulateSpamScore(data);
resultSpam.className = "spam-score";
if (spamScore === 1) {
resultSpam.classList.add("spam-low");
resultSpam.textContent = "Low spam risk";
} else if (spamScore === 2) {
resultSpam.classList.add("spam-medium");
resultSpam.textContent = "Medium spam risk";
} else {
resultSpam.classList.add("spam-high");
resultSpam.textContent = "High spam risk";
}
// Show result section
resultSection.style.display = "block";
recentSection.style.display = "block";
}
// Simulate spam score based on API data
function simulateSpamScore(data) {
if (!data.line_type) return 2;
if (data.line_type === "mobile") return 1;
if (data.line_type === "landline") return 2;
return 3; // VoIP/toll-free
}
// Add to recent searches
function addToRecentSearches(number, data) {
// Check if already in recent searches
const existingIndex = recentSearches.findIndex(
(item) => item.number === number
);
if (existingIndex !== -1) {
recentSearches.splice(existingIndex, 1);
}
// Add to beginning of array
recentSearches.unshift({
number: number,
name: data.carrier || "Unknown",
spamScore: simulateSpamScore(data),
country: data.country_name || "Unknown",
location: data.location || "Unknown",
international_format: data.international_format || `+${number}`,
});
// Keep only last 5 searches
if (recentSearches.length > 5) {
recentSearches.pop();
}
// Save to localStorage
localStorage.setItem("recentSearches", JSON.stringify(recentSearches));
// Update recent searches display
displayRecentSearches();
}
// Display recent searches
function displayRecentSearches() {
recentList.innerHTML = "";
if (recentSearches.length === 0) {
const li = document.createElement("li");
li.textContent = "No recent searches";
li.style.textAlign = "center";
li.style.color = "#a4b0be";
li.style.padding = "20px 0";
recentList.appendChild(li);
return;
}
recentSearches.forEach((item) => {
const li = document.createElement("li");
li.className = "recent-item";
// Format the number for display
const formattedNumber = item.international_format
? item.international_format
.replace("+", "+")
.replace(/(\d{2})(\d{3})(\d{3})(\d{4})/, "$1 $2 $3 $4")
: `+${item.number.substring(
0,
item.number.length - 10
)} ${item.number.substring(-10, 3)} ${item.number.substring(
3,
6
)} ${item.number.substring(6)}`;
li.innerHTML = `
<div>
<div class="recent-number">${formattedNumber}</div>
<div class="recent-name">${item.name}</div>
<div class="recent-name" style="font-size:12px;">${
item.country
}${item.location ? ` (${item.location})` : ""}</div>
</div>
<div class="recent-spam ${getSpamIndicatorClass(
item.spamScore
)}"></div>
`;
li.addEventListener("click", () => {
searchInput.value = formattedNumber;
searchNumber();
});
recentList.appendChild(li);
});
}
// Get spam indicator class
function getSpamIndicatorClass(score) {
if (score === 1) return "spam-indicator-low";
if (score === 2) return "spam-indicator-medium";
return "spam-indicator-high";
}
// Clear all recent searches
function clearRecentSearches() {
recentSearches = [];
localStorage.setItem("recentSearches", JSON.stringify(recentSearches));
displayRecentSearches();
}
// Toggle side menu
function toggleMenu() {
sideMenu.classList.toggle("active");
overlay.classList.toggle("active");
}
// Event Listeners
searchBtn.addEventListener("click", searchNumber);
searchInput.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
searchNumber();
}
});
clearAll.addEventListener("click", clearRecentSearches);
menuBtn.addEventListener("click", toggleMenu);
closeMenu.addEventListener("click", toggleMenu);
overlay.addEventListener("click", toggleMenu);
// Initialize
displayRecentSearches();
Key Changes for API Integration
- API Configuration:
- Added NumVerify API endpoint and API key placeholder
- You’ll need to sign up at numverify.com to get a free API key
- Updated Search Function:
- Replaced the mock database with actual API calls
- Added error handling for API failures
- Uses async/await for cleaner API call handling
- Result Processing:
displayResultFromAPI()
function processes the API response- Maps API data to our display format
- Includes simulated spam scoring (since NumVerify doesn’t provide this)
- Data Formatting:
- Improved phone number formatting based on API response
- Better handling of international numbers
Important Notes
- API Key Security: In a production app, you should never expose API keys in frontend code. Instead:
- Create a backend service that makes the API calls
- Have your frontend call your backend service
- CORS Issues: Some APIs may have CORS restrictions. You might need to:
- Configure CORS headers if you control the API
- Use a backend proxy for APIs you don’t control
- Rate Limiting: Free API tiers typically have limits (e.g., 100 requests/month). Monitor your usage.
- Privacy Compliance: Ensure your app complies with privacy laws regarding phone number lookup services.