/how-to-build-replit

How to Build a Marketplace with Replit

Learn how to build a fully functional marketplace using Replit. Follow step-by-step guidance to create, deploy, and scale your project easily.

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 Marketplace with Replit

To build a small working marketplace on Replit, you can use a simple Node.js + Express backend for your server and a React (or plain HTML/JS) frontend. Replit makes it possible to host both within the same Repl, store environment variables securely under the “Secrets” tab, and use a small in-memory or third-party database (like Replit DB, SQLite, or external services such as Supabase). The backend will handle routes like listing products, submitting an order, and fetching user data; while the frontend shows items and allows basic checkout simulation.

 

Set Up the Project Structure

 

When you first create a new Repl, choose the “Node.js” template. Inside your Repl, create the following folders and files:

  • server.js — your main backend server file.
  • public/ — folder for static assets like HTML, CSS, and JavaScript for the frontend.
  • database.js — optional small layer to interact with your data (could be a Replit DB or in-memory object).

 

// Inside the Replit shell
mkdir public
touch server.js
touch database.js
touch public/index.html
touch public/script.js
touch public/style.css

 

Backend: Creating a Simple Express Server

 

In server.js, initialize Express. This server will serve static frontend files and provide basic routes for fetching and posting marketplace data.

 

// server.js
import express from "express"
import bodyParser from "body-parser"
import { getProducts, addProduct } from "./database.js"

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

app.use(bodyParser.json())
app.use(express.static("public"))  // Serves all files from /public

// Route to get all products
app.get("/api/products", (req, res) => {
  res.json(getProducts())
})

// Route to add a product
app.post("/api/products", (req, res) => {
  const { name, price } = req.body
  if (!name || !price) {
    return res.status(400).json({ error: "Missing name or price" })
  }
  addProduct({ name, price })
  res.json({ message: "Product added successfully" })
})

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

 

This listens on Replit's dynamic port, which is already exposed via the “Run” button, so you don’t need extra deployment steps. The express.static middleware ensures files in the public folder can be accessed directly in your browser.

 

Basic Data Handling with Replit DB or In-memory Storage

 

Replit DB is a simple key-value database that persists between runs. You can store your marketplace items there.

Go to the Secrets tab in Replit (lock icon on the left bar) and set a key named REPLIT_DB_URL — it’s usually added automatically when using Replit DB package.

 

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

// In-memory fallback store
let localProducts = [
  { name: "Example Item 1", price: 10 },
  { name: "Example Item 2", price: 25 }
]

export async function getProducts() {
  const saved = await db.get("products")
  return saved || localProducts
}

export async function addProduct(product) {
  const products = await getProducts()
  products.push(product)
  await db.set("products", products)
}

 

If you don’t want persistence, you could skip the Replit DB import and simply use the localProducts array.

 

Frontend: HTML and Client-Side JavaScript

 

Inside public/index.html create a very simple layout displaying the products and a form to add new ones.

 

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Mini Marketplace</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>Marketplace</h1>
  <div id="products"></div>

  <h2>Add a new product</h2>
  <form id="addForm">
    <input id="name" type="text" placeholder="Item name" required />
    <input id="price" type="number" placeholder="Price" required />
    <button type="submit">Add</button>
  </form>

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

 

Now in your public/script.js file, fetch and render product data and manage the product form.

 

// public/script.js
const productDiv = document.getElementById("products")
const form = document.getElementById("addForm")

// Load existing products
async function loadProducts() {
  const res = await fetch("/api/products")
  const data = await res.json()
  productDiv.innerHTML = data.map(p => `<p><b>${p.name}</b>: $${p.price}</p>`).join("")
}

// Add new product
form.addEventListener("submit", async (e) => {
  e.preventDefault()
  const name = document.getElementById("name").value
  const price = parseFloat(document.getElementById("price").value)
  await fetch("/api/products", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name, price })
  })
  form.reset()
  loadProducts()
})

// Initial load
loadProducts()

 

Run and Test Inside Replit

 

  • Click the “Run” button. Replit installs dependencies automatically when detecting import statements.
  • Wait until you see “Server running” in the console output.
  • Click the preview window (or open the URL shown above the console). You should see your marketplace with sample items.

 

Enhancements and Production Notes

 

  • Secure data: Avoid exposing write operations publicly if you’re building a real app. Add simple token-based checks.
  • Persistence: Remember that Replit DB is simple but limited; if multiple users hit your endpoints a lot, consider an external database (Supabase, MongoDB Atlas).
  • Collaboration: You can click the “Invite” button for real-time editing — useful when pairing with teammates.
  • Deployment: Your Repl URL acts as a hosted service automatically; you don’t need separate hosting unless you outgrow it.

 

Following these steps gives you a fully functional small marketplace prototype inside Replit — real backend routes, real data persistence via Replit DB, and functional frontend integration. This approach matches how experienced Replit developers structure full-stack apps that stay stable and collaborative.

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 Create a Simple Marketplace API with Express and Replit Database



import express from "express";
import { v4 as uuidv4 } from "uuid";
import Database from "@replit/database";

const app = express();
const db = new Database();
app.use(express.json());

// Store new listing
app.post("/api/listings", async (req, res) => {
  const { sellerId, title, price, description } = req.body;
  if (!sellerId || !title || !price) return res.status(400).json({ error: "Missing fields" });

  const listingId = uuidv4();
  const listing = { listingId, sellerId, title, price, description, createdAt: Date.now() };

  await db.set(`listing:${listingId}`, listing);
  res.json(listing);
});

// Get all listings
app.get("/api/listings", async (req, res) => {
  const keys = await db.list("listing:");
  const listings = [];
  for (const key of keys) {
    const item = await db.get(key);
    if (item) listings.push(item);
  }
  res.json(listings);
});

// Buy item (simple transaction simulation)
app.post("/api/buy/:id", async (req, res) => {
  const { id } = req.params;
  const buyerId = req.body.buyerId;
  const listing = await db.get(`listing:${id}`);
  if (!listing) return res.status(404).json({ error: "Listing not found" });
  if (listing.sold) return res.status(400).json({ error: "Already sold" });

  listing.sold = true;
  listing.buyerId = buyerId;
  listing.soldAt = Date.now();
  await db.set(`listing:${id}`, listing);

  res.json({ success: true, listing });
});

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

How to Add Stripe Payments to Your Replit Marketplace



import express from "express";
import fetch from "node-fetch";

const app = express();
app.use(express.json());

const STRIPE_SECRET = process.env.STRIPE_SECRET;
const FRONTEND_URL = process.env.FRONTEND_URL || "https://your-repl-name.username.repl.co";

// Create Stripe Checkout Session (used when buyer clicks “Buy”)
app.post("/api/create-checkout-session", async (req, res) => {
  try {
    const { listingId, title, amount } = req.body;
    const response = await fetch("https://api.stripe.com/v1/checkout/sessions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${STRIPE_SECRET}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        line_items\[0]\[price_data][currency]: "usd",
        line_items\[0]\[price_data]\[product\_data]\[name]: title,
        line_items\[0]\[price_data][unit\_amount]: String(amount \* 100),
        line\_items\[0]\[quantity]: "1",
        mode: "payment",
        success\_url: `${FRONTEND_URL}/success?listing=${listingId}`,
        cancel\_url: `${FRONTEND_URL}/cancel`,
      }),
    });

    const data = await response.json();
    if (data.error) return res.status(400).json(data.error);
    res.json({ id: data.id, url: data.url });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Failed to create checkout session" });
  }
});

// Stripe Webhook (verify payment success)
app.post("/api/stripe-webhook", express.raw({ type: "application/json" }), async (req, res) => {
  try {
    const event = JSON.parse(req.body);

    if (event.type === "checkout.session.completed") {
      const session = event.data.object;
      const listingId = new URL(session.success\_url).searchParams.get("listing");
      console.log("Payment successful for listing:", listingId);
      // In a real app, mark listing as sold in your database here.
    }

    res.json({ received: true });
  } catch (err) {
    console.error("Webhook error:", err);
    res.status(400).send(`Webhook error: ${err.message}`);
  }
});

app.listen(3001, () => console.log("Payments microservice running on port 3001"));

How to Upload Images to Cloudinary in Your Replit Marketplace



import express from "express";
import multer from "multer";
import { v2 as cloudinary } from "cloudinary";
import streamifier from "streamifier";

const app = express();

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD\_NAME,
  api_key: process.env.CLOUDINARY_API\_KEY,
  api_secret: process.env.CLOUDINARY_API\_SECRET,
});

const upload = multer();

app.post("/api/upload-image", upload.single("file"), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: "No file provided" });

  try {
    const uploadStream = cloudinary.uploader.upload\_stream(
      { folder: "marketplace-listings", resource\_type: "image" },
      (error, result) => {
        if (error) return res.status(500).json({ error: "Upload failed" });
        res.json({ url: result.secure\_url });
      }
    );

    streamifier.createReadStream(req.file.buffer).pipe(uploadStream);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Unexpected server error" });
  }
});

app.listen(3002, () => console.log("Image upload service running on port 3002"));

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 Marketplace with Replit

Building a marketplace on Replit is absolutely possible if you design it with Replit’s environment in mind: keep it lightweight, use hosted databases, store environment variables securely, and separate your frontend and backend cleanly. The best practice is to use a Node.js (Express) backend connected to a persistent database (like Supabase or MongoDB Atlas), and a React frontend — either in the same Repl (monorepo style) or as two connected Repls. Handle payments through a third-party API like Stripe (never store card data yourself). Use Replit Secrets for sensitive keys, and develop with Replit’s built-in web server rather than treating it like your local environment — meaning, no relying on local storage or complex background tasks that Replit might suspend when idle.

 

Project Structure

 

For simplicity, let's assume everything is in one Repl. Your structure should look like this:

├── index.js             // Node.js backend entry file
├── package.json
├── client/              // React app (frontend)
│   ├── src/
│   └── package.json
├── .replit
├── replit.nix           // Replit environment definition (auto-generated)

 

Run both backend and frontend using Replit’s “Run” command — use npm-run-all or concurrently to start them together.

// package.json
{
  "scripts": {
    "start": "node index.js",
    "client": "npm start --prefix client",
    "dev": "concurrently \"npm run start\" \"npm run client\""
  },
  "dependencies": {
    "express": "^4.18.2",
    "dotenv": "^16.0.3",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "concurrently": "^8.0.1"
  }
}

Then in Replit, set the run command to npm run dev.

 

Backend Setup (index.js)

 

Your backend should handle user accounts, listing management, and payments through APIs, not manual credit card handling. Make sure your server listens on process.env.PORT (Replit automatically assigns it).

// index.js
import express from "express"
import cors from "cors"
import dotenv from "dotenv"
dotenv.config()

const app = express()
app.use(cors())
app.use(express.json())

// Simple route to confirm server is running
app.get("/", (req, res) => {
  res.send("Marketplace backend running successfully!")
})

// Example of listing endpoint (simple in-memory demo)
let listings = []   // Replace with real database later

app.post("/api/listings", (req, res) => {
  const { title, price } = req.body
  if(!title || !price) return res.status(400).send("Missing fields")
  const newListing = { id: Date.now(), title, price }
  listings.push(newListing)
  res.status(201).json(newListing)
})

app.get("/api/listings", (req, res) => {
  res.json(listings)
})

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

Where to place: Create this file directly in the root of your Repl as index.js.

 

Connecting to a Real Database

 

Replit’s filesystem resets on each deploy, so use a proper external database. Supabase (Postgres) is good and has a simple REST API. Or use MongoDB Atlas if you’re more comfortable with JavaScript objects.

Store your database URL in Replit Secrets:

  • Open the left sidebar → Tools → Secrets → Add new secret: key = DATABASE\_URL, value = your database connection string.

Change your backend code:

import { MongoClient } from "mongodb"
const client = new MongoClient(process.env.DATABASE_URL)
await client.connect()
const db = client.db("marketplace")
const listingsCollection = db.collection("listings")

app.post("/api/listings", async (req, res) => {
  const { title, price } = req.body
  const result = await listingsCollection.insertOne({ title, price })
  res.status(201).json(result)
})

 

Frontend Setup (React, in client/ folder)

 

Inside client/src/, create a simple page to fetch and display listings:

// client/src/App.js
import React, { useEffect, useState } from "react"

function App() {
  const [listings, setListings] = useState([])

  useEffect(() => {
    fetch("/api/listings")
      .then(res => res.json())
      .then(data => setListings(data))
  }, [])

  return (
    <div>
      <h1>Marketplace Listings</h1>
      <ul>
        {listings.map((item) => (
          <li key={item.id}>{item.title} - ${item.price}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

Where to place: inside client/src/App.js. Then in client/package.json, set your proxy so it can talk to your Replit backend:

// client/package.json
{
  "proxy": "http://localhost:3000"
}

 

Best Replit-Specific Practices

 

  • Always use Replit Secrets for API keys (Stripe, DB, etc.). Don’t hardcode them in files.
  • Use process.env.PORT for your server port. Replit dynamically assigns it — never hardcode 3000.
  • Don’t rely on local storage for saving data — it’ll reset when Replit sleeps or redeploys.
  • Use external APIs for payment (Stripe Checkout, PayPal SDK).
  • Git integration: Connect your Repl to GitHub only after setting up .gitignore to exclude node\_modules and replit-specific files.
  • Collaboration: If multiple users collaborate live, avoid editing package.json at the same time — it can break dependencies. Communicate in comments.
  • Deployments: Use Replit Deployments for stable versions (round green Deploy button). Deployments are durable and won’t go to sleep like regular Repls.

 

Next Steps to Production

 

  • Set up OAuth (Supabase Auth or Clerk) for user registration and login.
  • Integrate Stripe Checkout for payments using frontend redirect links, never storing cards yourself.
  • Use environment variables in Replit Secrets for API keys.
  • Once stable, “Deploy” the Repl. Use the deployed URL for your production frontend/backend communication.

 

This structure and workflow follow the way real developers ship full apps on Replit without hitting its persistence or background limits, while keeping everything maintainable and secure.

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