Learn how to build a directory service using Replit. Follow easy steps to create, host, and manage your own powerful online directory.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
If you want to build a simple directory service (like a phone book, employee list, or contacts directory) inside Replit, the cleanest approach is to use a small Node.js + Express backend with a lightweight SQLite database or JSON file for storage. Replit runs Node apps very well and gives you a built-in web server environment that’s always live when you click “Run.” You can start with the Replit Node.js template and then add your backend routes, database connection, and a small frontend page to display or manage your directory.
Start a new Repl using the “Node.js” template. You’ll see a index.js file and a package.json. The index.js file is your main entry point (this is what Replit runs automatically).
Open the Shell tab (bottom pane) and install Express and SQLite:
npm install express sqlite3
In your existing index.js file, replace any sample code with this. This sets up an Express server and connects to SQLite to hold your directory data:
// index.js
const express = require('express')
const sqlite3 = require('sqlite3').verbose()
const path = require('path')
const app = express()
// Middleware to parse JSON data from requests
app.use(express.json())
// Initialize database file inside repl folder
const db = new sqlite3.Database('./directory.db')
// Create table if not exists
db.run(`CREATE TABLE IF NOT EXISTS people (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL,
email TEXT
)`)
// Serve static files (frontend) from "public"
app.use(express.static(path.join(__dirname, 'public')))
// Route to get all directory entries
app.get('/api/people', (req, res) => {
db.all('SELECT * FROM people', [], (err, rows) => {
if (err) return res.status(500).send(err)
res.json(rows)
})
})
// Route to add a new entry
app.post('/api/people', (req, res) => {
const { name, phone, email } = req.body
if (!name || !phone) return res.status(400).send('Name and phone required')
db.run(`INSERT INTO people (name, phone, email) VALUES (?, ?, ?)`, [name, phone, email], function (err) {
if (err) return res.status(500).send(err)
res.status(201).json({ id: this.lastID, name, phone, email })
})
})
const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Server running on port ${port}`))
This file both handles backend routes and serves the frontend from a folder we’ll add next.
In your Replit sidebar, click “New Folder” → name it public. Inside that folder, click “New File” → name it index.html. Replit will automatically serve it because of Express’ static middleware above.
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Directory Service</title>
</head>
<body>
<h1>Directory</h1>
<div id="directory"></div>
<form id="addForm">
<input name="name" placeholder="Name" required />
<input name="phone" placeholder="Phone" required />
<input name="email" placeholder="Email" />
<button type="submit">Add</button>
</form>
<script>
async function loadDirectory() {
const res = await fetch('/api/people')
const data = await res.json()
document.getElementById('directory').innerHTML =
data.map(p => `<div><strong>${p.name}</strong> - ${p.phone} (${p.email || ''})</div>`).join('')
}
document.getElementById('addForm').onsubmit = async (e) => {
e.preventDefault()
const form = e.target
const newPerson = {
name: form.name.value,
phone: form.phone.value,
email: form.email.value
}
await fetch('/api/people', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPerson)
})
form.reset()
loadDirectory()
}
loadDirectory() // initial load
</script>
</body>
</html>
Now when you click “Run” in Replit, Replit will start your Express server and open a webview tab with the directory UI.
process.env.MY\_SECRET in your code. Don’t hardcode secrets inside code files.
express.json() before using req.body. Without it, req.body will be undefined.
This setup gives you a working small-scale directory service in Replit: an Express backend, SQLite data store, and simple HTML frontend all running together in one Repl. It’s fast to iterate, easy to understand, and 100% real.
const express = require("express");
const Database = require("@replit/database");
const db = new Database();
const app = express();
app.use(express.json());
// Normalize directory entry data
function normalizeEntry(entry) {
return {
id: entry.id || Date.now().toString(),
name: entry.name.trim(),
category: entry.category?.toLowerCase() || "general",
contact: {
email: entry.contact?.email || "",
phone: entry.contact?.phone || ""
},
meta: {
createdAt: entry.meta?.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString()
}
};
}
// Create or update directory entry
app.post("/api/directory", async (req, res) => {
const entry = normalizeEntry(req.body);
await db.set(entry.id, entry);
res.json({ success: true, data: entry });
});
// Filtered directory listing
app.get("/api/directory", async (req, res) => {
const category = req.query.category?.toLowerCase();
const keys = await db.list();
const entries = await Promise.all(keys.map(k => db.get(k)));
const filtered = category
? entries.filter(e => e.category === category)
: entries;
res.json({ count: filtered.length, data: filtered });
});
app.listen(3000, () => console.log("Directory API running on port 3000"));
const express = require("express");
const axios = require("axios");
const Database = require("@replit/database");
const db = new Database();
const app = express();
app.use(express.json());
// Example: Enrich a directory entry's location using external geocoding API before saving
app.post("/api/directory/location", async (req, res) => {
try {
const { name, address } = req.body;
if (!address) return res.status(400).json({ error: "Address is required" });
// Avoid exposing secrets in client code, use Replit Secrets instead
const GEOCODE_API = process.env.GEOCODE_API\_KEY;
const geoResp = await axios.get("https://maps.googleapis.com/maps/api/geocode/json", {
params: { address, key: GEOCODE\_API }
});
if (!geoResp.data.results.length)
return res.status(404).json({ error: "Address not found" });
const locData = geoResp.data.results[0].geometry.location;
const entry = {
id: Date.now().toString(),
name,
address,
coordinates: locData,
createdAt: new Date().toISOString()
};
await db.set(entry.id, entry);
res.json({ success: true, data: entry });
} catch (err) {
console.error("Error saving entry:", err.message);
res.status(500).json({ error: "Server error" });
}
});
// Example route to retrieve entries within distance (basic filter)
app.get("/api/directory/nearby", async (req, res) => {
const { lat, lng, range = 10 } = req.query;
if (!lat || !lng) return res.status(400).json({ error: "lat and lng required" });
const entries = await Promise.all((await db.list()).map(k => db.get(k)));
const toRad = deg => deg \* (Math.PI / 180);
const nearby = entries.filter(e => {
if (!e.coordinates) return false;
const R = 6371; // km
const dLat = toRad(e.coordinates.lat - lat);
const dLng = toRad(e.coordinates.lng - lng);
const a =
Math.sin(dLat / 2) \*\* 2 +
Math.cos(toRad(lat)) _ Math.cos(toRad(e.coordinates.lat)) _ Math.sin(dLng / 2) \*\* 2;
const c = 2 \* Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R \* c;
return distance <= range;
});
res.json({ count: nearby.length, data: nearby });
});
app.listen(3000, () => console.log("Directory API with geocoding running"));
const express = require("express");
const Database = require("@replit/database");
const db = new Database();
const app = express();
app.use(express.json());
// Automatically remove stale directory entries older than N days (housekeeping route)
async function purgeStaleEntries(days) {
const cutoff = Date.now() - days _ 24 _ 60 _ 60 _ 1000;
const keys = await db.list();
let removed = 0;
for (const key of keys) {
const entry = await db.get(key);
if (entry?.meta?.updatedAt && new Date(entry.meta.updatedAt).getTime() < cutoff) {
await db.delete(key);
removed++;
}
}
return removed;
}
// Trigger purge via cron or manual HTTP call (e.g., Replit scheduler)
app.post("/api/admin/purge", async (req, res) => {
const { days = 30, secret } = req.body;
if (secret !== process.env.ADMIN\_SECRET)
return res.status(403).json({ error: "Invalid admin secret" });
const removed = await purgeStaleEntries(days);
res.json({ success: true, removed });
});
app.listen(3000, () => console.log("Directory cleanup API active"));

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 good, reliable way to build a Directory Service on Replit is to use Node.js with Express for your backend API, Replit’s built-in database (Replit DB) or an external database for storing entries, and, if needed, a small front-end (HTML/React) to display and interact with the directory. Keep your data access logic and server code separate for clarity, and store any sensitive data (API keys, DB URLs) in the Replit “Secrets” tab. Always be careful not to treat Replit like a full local server—its filesystem resets on rebuilds and public Repls expose ports. Use deployment tools and environment variables properly.
Create the following structure directly inside your Replit project (you can do this from the Files sidebar):
.
├── index.js
├── routes/
│ └── directory.js
├── data/
│ └── db.js
├── views/
│ └── index.html
└── package.json
In the Replit Shell (bottom pane), run:
npm init -y
npm install express
(Optionally add node-fetch or replit-database if you want storage)
npm install replit-database
This is your main service file. You can write this in index.js located in your root directory:
// index.js
const express = require("express")
const app = express()
const directoryRoutes = require("./routes/directory")
app.use(express.json()) // to parse JSON requests
// attach route to /api/directory
app.use("/api/directory", directoryRoutes)
// basic homepage route
app.get("/", (req, res) => {
res.send("Directory service is running.")
})
// Replit needs this PORT pattern
const PORT = process.env.PORT || 3000
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
This file holds endpoints for your directory entries. Create routes/directory.js:
// routes/directory.js
const express = require("express")
const router = express.Router()
const db = require("../data/db")
// Get all contacts
router.get("/", async (req, res) => {
const entries = await db.getAll()
res.json(entries)
})
// Add a new contact
router.post("/", async (req, res) => {
const { name, email } = req.body
if (!name || !email) {
return res.status(400).json({ message: "Name and email required" })
}
const newEntry = await db.addEntry(name, email)
res.status(201).json(newEntry)
})
module.exports = router
If you don’t want to use external storage, the Replit Database is fine for small projects. Create data/db.js:
// data/db.js
const Database = require("@replit/database")
const db = new Database()
// Return all entries
async function getAll() {
const keys = await db.list()
const entries = []
for (let key of keys) {
const value = await db.get(key)
entries.push(value)
}
return entries
}
// Add one entry
async function addEntry(name, email) {
const id = Date.now().toString()
const item = { id, name, email }
await db.set(id, item)
return item
}
module.exports = { getAll, addEntry }
If you were connecting to a real database service (like MongoDB Atlas), you’d never hardcode the credentials. Open the “🔑 Secrets” panel on the left of your Replit and add variables (like DB_URI). Then you can use it as process.env.DB_URI in your server code.
Click “Run” on top of Replit. Then open the URL Replit gives you — something like https://yourproject.username.repl.co.
You can test the API with the built-in Replit Webview or using curl commands:
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "[email protected]"}' \
https://yourproject.username.repl.co/api/directory
replit-database for persistence; Replit file changes outside of it can vanish on rebuilds.
If you want a minimal webpage to show your directory, create views/index.html and serve it by adjusting your route. You can use vanilla HTML or React depending on complexity.
<!DOCTYPE html>
<html>
<head>
<title>Directory Service</title>
</head>
<body>
<h1>Directory</h1>
<div id="directory"></div>
<script>
fetch('/api/directory')
.then(response => response.json())
.then(data => {
const list = document.getElementById('directory')
data.forEach(item => {
const p = document.createElement('p')
p.textContent = item.name + " - " + item.email
list.appendChild(p)
})
})
</script>
</body>
</html>
This gives you a functioning, database-backed directory service hosted on Replit. Keep your code modular, make backups, and use Secrets for anything private.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.