/how-to-build-replit

How to Build a Classifieds with Replit

Learn how to build a powerful classifieds app using Replit. Follow this simple guide to create, deploy, and customize your own classifieds platform.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Build a Classifieds with Replit

You can build a simple classifieds app on Replit using Node.js, Express, and a small JSON-based or Replit Database (key-value) storage. The workflow is: users can post ads (title, description, contact info), and others can browse or search them. Replit handles hosting, collaboration, and environment variables — so you can deploy and share your project easily. The simplest version can live entirely in one Repl. Use server.js as your backend entry file, public/ for frontend HTML/JS/CSS, and Replit’s built-in Database for persistence.

 

Setup the Project

 

Start a new Repl using the Node.js template. Replit automatically gives you an environment with Node and npm ready. Inside the file tree on the left, you will see index.js by default. Rename it to server.js for clarity because it will be your Express server entry point.

  • Install Express using the Shell tab:
npm install express
  • Also install Replit Database for simple storage:
npm install @replit/database

 

Basic Server Setup

 

In server.js, add this base code. It sets up a simple Express web server that can handle browser requests.

const express = require("express");
const app = express();
const Database = require("@replit/database");
const db = new Database();
const PORT = 3000;

// Allow Express to handle JSON sent from frontend forms
app.use(express.json());
app.use(express.static("public"));

// Basic home route
app.get("/", (req, res) => {
  res.sendFile(__dirname + "/public/index.html");
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

 

Create Frontend Files

 

Now, make a public/ folder in the left sidebar. Inside it, create:

  • index.html – the main page with form and listings
  • script.js – handles client-side logic (sending posts, fetching ads)
  • style.css – optional styling

 

index.html

 

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Replit Classifieds</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>Classified Ads</h1>

  <form id="adForm">
    <input type="text" id="title" placeholder="Title" required><br>
    <textarea id="description" placeholder="Description" required></textarea><br>
    <input type="text" id="contact" placeholder="Contact Info" required><br>
    <button type="submit">Post Ad</button>
  </form>

  <h2>All Listings</h2>
  <div id="ads"></div>

  <script src="script.js"></script>
</body>
</html>

 

script.js

 

// Fetch and show all ads
async function loadAds() {
  const res = await fetch("/ads");
  const ads = await res.json();
  const list = document.getElementById("ads");
  list.innerHTML = "";
  ads.forEach(a => {
    const item = document.createElement("div");
    item.innerHTML = `<h3>${a.title}</h3><p>${a.description}</p><small>Contact: ${a.contact}</small>`;
    list.appendChild(item);
  });
}

document.getElementById("adForm").addEventListener("submit", async (e) => {
  e.preventDefault();
  const ad = {
    title: document.getElementById("title").value,
    description: document.getElementById("description").value,
    contact: document.getElementById("contact").value
  };
  await fetch("/ads", { 
    method: "POST", 
    headers: { "Content-Type": "application/json" }, 
    body: JSON.stringify(ad)
  });
  loadAds(); // refresh after post
});

loadAds();

 

Backend Routes for Ads

 

Back in server.js, under your other routes, add logic for creating and fetching ads. You’ll use Replit Database to store them persistently.

// Get all ads
app.get("/ads", async (req, res) => {
  let ads = (await db.get("ads")) || [];
  res.json(ads);
});

// Add new ad
app.post("/ads", async (req, res) => {
  let ads = (await db.get("ads")) || [];
  const newAd = {
    title: req.body.title,
    description: req.body.description,
    contact: req.body.contact,
    time: new Date().toISOString()
  };
  ads.push(newAd);
  await db.set("ads", ads);
  res.json({ success: true });
});

 

How This Works in Replit

 

  • server.js runs automatically
  • public/ content is served statically
  • @replit/database keeps persistent data across runs, specific to your Repl
  • You can press the green “Run” button, and Replit will give you a live URL to access your classifieds app immediately

 

Handling Secrets

 

If later you add Admin routes or need an API key (for example, external email or image upload service), store them in Secrets (Environment Variables) by opening the padlock icon in the left sidebar. Then inside code you can access them by process.env.MY_SECRET_NAME.

 

Common Pitfalls and Tips

 

  • Do not store private info in the frontend — keep secret keys and sensitive data in Replit Secrets only.
  • Database mismatch problems: Replit Database stores JSON as strings; make sure you always set/get parsed JSON arrays, as shown.
  • Caching issue: Sometimes old JS/CSS lives in the browser cache — open in Incognito or click "Hard Refresh" (Ctrl+F5) to test changes.
  • Autosave is on — so version with Git (through Replit integrated Git panel) if you want checkpoints before big edits.

 

Next Steps for Growth

 

You can later extend this with a search feature (filtering ads list in memory before render), add image uploads using a simple third-party service, or connect a full Postgres or MongoDB instance if the Replit Database becomes too simple. But the structure above is a real, functional classifieds platform that runs entirely on Replit’s free tier and demonstrates a true full-stack setup.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us

How to Build a Simple Classifieds API with Express and Replit Database



import express from "express";
import Database from "@replit/database";

const app = express();
const db = new Database();

app.use(express.json());

// Utility to generate unique IDs for listings
const genId = () => "listing\_" + Math.random().toString(36).substr(2, 9);

// POST /api/listings - create a new classified listing
app.post("/api/listings", async (req, res) => {
  const { title, description, price, category, userId } = req.body;
  if (!title || !price || !userId)
    return res.status(400).json({ error: "Missing required fields" });

  const id = genId();
  const newListing = {
    id,
    title,
    description: description || "",
    price: parseFloat(price),
    category: category || "general",
    userId,
    createdAt: Date.now(),
  };

  await db.set(id, newListing);
  res.json({ success: true, listing: newListing });
});

// GET /api/listings?category=something - fetch listings by category
app.get("/api/listings", async (req, res) => {
  const { category } = req.query;
  const keys = await db.list("listing\_");
  const allListings = await Promise.all(keys.map(key => db.get(key)));
  const filtered = category
    ? allListings.filter(l => l.category === category)
    : allListings;
  res.json(filtered.sort((a, b) => b.createdAt - a.createdAt));
});

app.listen(3000, () => console.log("✅ Classifieds API running on port 3000"));

How to Add Geolocation to Your Classifieds Listings with Replit



import express from "express";
import fetch from "node-fetch";
import Database from "@replit/database";

const app = express();
const db = new Database();

app.use(express.json());

// Store your API key safely in Replit Secrets as GEO_API_KEY
const GEO_API_URL = "https://api.mapbox.com/geocoding/v5/mapbox.places/";

app.post("/api/listings/with-location", async (req, res) => {
  const { title, description, price, address, userId } = req.body;
  if (!title || !price || !address || !userId)
    return res.status(400).json({ error: "Missing required fields" });

  try {
    // Geocode address using external API (Mapbox in this example)
    const geoRes = await fetch(
      `${GEO_API_URL}${encodeURIComponent(address)}.json?access_token=${process.env.GEO_API_KEY}`
    );
    const geoData = await geoRes.json();
    const coordinates = geoData.features?.[0]?.center || [0, 0];

    const id = "listing\_" + Math.random().toString(36).substr(2, 9);
    const listing = {
      id,
      title,
      description: description || "",
      price: parseFloat(price),
      address,
      location: { lng: coordinates[0], lat: coordinates[1] },
      userId,
      createdAt: Date.now(),
    };

    await db.set(id, listing);
    res.json({ success: true, listing });
  } catch (err) {
    console.error("Error creating listing:", err);
    res.status(500).json({ error: "Failed to geocode address" });
  }
});

app.listen(3000, () => console.log("✅ Classifieds API with geolocation ready"));

How to Build a Secure Image Upload API for Your Classifieds on Replit



import express from "express";
import multer from "multer";
import { createHash } from "crypto";
import Database from "@replit/database";
import fs from "fs";

const app = express();
const db = new Database();
const upload = multer({ dest: "uploads/" });

// Secure upload endpoint for classified listing images (stored by hash)
app.post("/api/upload", upload.single("file"), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: "No file uploaded" });

  try {
    // Generate hash for deduplication
    const fileData = fs.readFileSync(req.file.path);
    const hash = createHash("sha256").update(fileData).digest("hex");
    const filename = `uploads/${hash}_${req.file.originalname}`;

    // Avoid duplicates and keep storage minimal in Replit
    if (!fs.existsSync(filename)) {
      fs.renameSync(req.file.path, filename);
    } else {
      fs.unlinkSync(req.file.path);
    }

    // Store file metadata in the DB
    const fileRecord = {
      hash,
      filename,
      uploadedAt: Date.now(),
    };
    await db.set(`file_${hash}`, fileRecord);

    res.json({
      success: true,
      fileUrl: `/images/${hash}`,
    });
  } catch (err) {
    console.error("Upload error:", err);
    res.status(500).json({ error: "Upload failed" });
  }
});

// Serve uploaded images safely
app.get("/images/:hash", async (req, res) => {
  const record = await db.get(`file_${req.params.hash}`);
  if (!record) return res.status(404).send("Not found");
  res.sendFile(record.filename, { root: "." });
});

app.listen(3000, () => console.log("✅ Upload API ready on port 3000"));

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Best Practices for Building a Classifieds with Replit

To build a Classifieds app on Replit in a realistic, maintainable way, you’ll want to use a small Node.js + Express backend, a persistent database (like Replit’s built-in Replit Database or an external one like MongoDB Atlas), and a lightweight frontend that can be static HTML or React. Use Replit’s Secrets to keep private keys and DB URLs secure. Store user images using Replit’s file storage or external services. The most important best practices are: separate backend and frontend logic properly, don’t commit secrets, avoid blocking operations on the main thread, use the Replit shell and Console for logs, and test frequently by opening the webview link. Replit works best for small to midsize apps and MVPs — so optimize for simplicity and quick iteration rather than over-engineering.

 

Set up your project structure

 

Create these files and folders in your Replit project root:

  • index.js — your Node.js server (backend entry point)
  • public/ — a folder containing static files (HTML, CSS, images)
  • views/ — if you use a template engine like EJS
  • database.js — logic for database connection
  • package.json — auto-created when you make a Node Repl, holds dependencies

 

// In the Replit Shell, install Express
npm install express

 

Basic Express server

 

Open index.js (or create it if missing) and insert this code. It will handle basic routes like listing and posting ads.

 

// index.js
const express = require("express")
const path = require("path")
const db = require("./database") // we’ll create this next

const app = express()
const PORT = process.env.PORT || 3000

app.use(express.json()) // parse incoming JSON
app.use(express.static(path.join(__dirname, "public")))

// Route to list all classifieds
app.get("/api/classifieds", async (req, res) => {
  const items = await db.getAll()
  res.json(items)
})

// Route to post a new classified ad
app.post("/api/classifieds", async (req, res) => {
  const { title, description, contact } = req.body
  if (!title || !description) return res.status(400).json({ error: "Missing fields" })
  const newItem = await db.insert({ title, description, contact })
  res.json(newItem)
})

app.listen(PORT, () => console.log("Server running on port", PORT))

 

Setting up a persistent database

 

The simplest option inside Replit is Replit Database (@replit/database package). It’s lightweight, key-value based, and stored per Repl, so great for small apps and learning.

 

npm install @replit/database

 

Create a new file named database.js in the root folder and insert:

 

// database.js
const Database = require("@replit/database")
const db = new Database()

async function getAll() {
  const keys = await db.list()
  const records = []
  for (const key of keys) {
    const item = await db.get(key)
    records.push(item)
  }
  return records
}

async function insert(item) {
  const id = "ad_" + Date.now()
  await db.set(id, { id, ...item })
  return await db.get(id)
}

module.exports = { getAll, insert }

 

Frontend basics

 

Create a public/index.html file. This will be your homepage listing ads and a simple form to post new ones:

 

<!DOCTYPE html>
<html>
  <head>
    <title>Replit Classifieds</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <h1>Replit Classifieds</h1>
    <div id="ads"></div>

    <h2>Post a new ad</h2>
    <form id="adForm">
      <input type="text" name="title" placeholder="Title" required /><br>
      <textarea name="description" placeholder="Description" required></textarea><br>
      <input type="text" name="contact" placeholder="Contact info" /><br>
      <button type="submit">Post Ad</button>
    </form>

    <script>
      async function loadAds() {
        const res = await fetch("/api/classifieds")
        const ads = await res.json()
        document.getElementById("ads").innerHTML = ads.map(ad => 
          `<div><h3>${ad.title}</h3><p>${ad.description}</p><small>${ad.contact || ""}</small></div>`
        ).join("")
      }

      document.getElementById("adForm").addEventListener("submit", async (e) => {
        e.preventDefault()
        const form = e.target
        const ad = {
          title: form.title.value,
          description: form.description.value,
          contact: form.contact.value
        }
        await fetch("/api/classifieds", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(ad)
        })
        form.reset()
        loadAds()
      })

      loadAds()
    </script>
  </body>
</html>

 

Using Secrets and Environment Variables

 

If you connect to external APIs or databases, never hardcode credentials. In Replit, click the “🔒 Secrets” tab on the left, add keys, and access them via process.env.KEY\_NAME in Node.js.

 

// Example to use MongoDB Atlas
const MONGO_URI = process.env.MONGO_URI

 

Replit deployment and debugging best practices

 

  • Run and Test Often: The “Run” button starts your server. Open the webview tab to see changes live.
  • Check the Console: Use console.log() to debug. The console output helps trace backend issues fast.
  • Version Control: Connect GitHub in the left sidebar to push the Repl as a real repo. Replit’s Git integration is stable now.
  • Prevent Repl Sleeping: On free accounts, your Repl sleeps after inactivity. Consider using Replit Teams Pro or deploy externally if you need constant uptime.
  • Split Logic: Keep Node server code and frontend HTML/React in separate folders for clarity and easier collaboration.

 

Production and Scalability Notes

 

Replit is great for prototyping and small-project hosting, but for a full classifieds marketplace:

  • Move data storage to MongoDB Atlas or Supabase for reliability.
  • Use a separate image host (e.g., Cloudinary) for pictures.
  • Keep backend logic small and stateless to make deployment portable.
  • Use Replit’s “Deployments” panel for a stable HTTPS production endpoint when ready.

 

Following this approach keeps your Classifieds app manageable in Replit — clean separation of frontend, backend, and data, secure handling of secrets, and easy debugging in the Replit environment.

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022