Learn how to build a reviews and ratings system using Replit. Follow this step-by-step guide to create, store, and display user feedback easily.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build a simple and working reviews & ratings system on Replit, you can use a Node.js + Express backend along with a small JSON file (or a database if you prefer) to store reviews. You can then connect it with a frontend (plain HTML or React). Below is a straightforward, working method that runs perfectly on Replit, uses environment variables correctly, handles reviews, and is deployed automatically once you click “Run” in Replit.
Create a new Repl using Node.js template (not HTML, CSS, JS). Replit will make files like index.js and package.json for you.
In index.js, paste the following code. This file runs your Express server. It can read and write reviews from reviews.json.
// index.js
const express = require("express")
const fs = require("fs")
const path = require("path")
const app = express()
// Middleware that allows JSON body parsing
app.use(express.json())
app.use(express.static("public")) // serves HTML in /public
const dataFile = path.join(__dirname, "reviews.json")
// Helper to read data
function readReviews() {
try {
const data = fs.readFileSync(dataFile)
return JSON.parse(data)
} catch (err) {
return [] // if empty file or not found
}
}
// Helper to write data
function writeReviews(reviews) {
fs.writeFileSync(dataFile, JSON.stringify(reviews, null, 2))
}
// GET route - list all reviews
app.get("/api/reviews", (req, res) => {
const reviews = readReviews()
res.json(reviews)
})
// POST route - add a review
app.post("/api/reviews", (req, res) => {
const { name, rating, comment } = req.body
if (!name || !rating) {
return res.status(400).json({ message: "Name and rating are required." })
}
const reviews = readReviews()
const newReview = {
id: Date.now(),
name,
rating,
comment: comment || ""
}
reviews.push(newReview)
writeReviews(reviews)
res.status(201).json(newReview)
})
// Start the server on Replit port
const port = process.env.PORT || 3000
app.listen(port, () => console.log("Server running on port " + port))
Where to put this:
[]
Inside the public folder, create a new file named index.html and add:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Reviews & Ratings</title>
</head>
<body>
<h1>Leave a Review</h1>
<form id="reviewForm">
<input type="text" id="name" placeholder="Your name" required /><br />
<input type="number" id="rating" placeholder="Rating (1-5)" min="1" max="5" required /><br />
<textarea id="comment" placeholder="Your comment"></textarea><br />
<button type="submit">Submit</button>
</form>
<h2>All Reviews</h2>
<ul id="reviewsList"></ul>
<script>
const form = document.getElementById("reviewForm")
const list = document.getElementById("reviewsList")
async function loadReviews() {
const res = await fetch("/api/reviews")
const reviews = await res.json()
list.innerHTML = ""
reviews.forEach(r => {
const item = document.createElement("li")
item.textContent = `${r.name} (${r.rating}/5): ${r.comment}`
list.appendChild(item)
})
}
form.addEventListener("submit", async e => {
e.preventDefault()
const name = document.getElementById("name").value
const rating = document.getElementById("rating").value
const comment = document.getElementById("comment").value
await fetch("/api/reviews", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, rating, comment })
})
form.reset()
loadReviews()
})
loadReviews() // load reviews on page open
</script>
</body>
</html>
Where to put this:
Press Run. Replit starts the Express server automatically and shows a public URL (on the right). Open it in the browser tab. Submit a few sample reviews, then refresh the page — you’ll see them persist (Replit keeps them until container resets).
If you want permanent storage, move from a JSON file to a small hosted database like Replit DB or Supabase. For lightweight use, JSON suffices in developing phase.
path.join(\_\_dirname, ...) to avoid path issues inside Replit.
This setup gives you a clean, working Reviews & Ratings system that runs fully inside Replit, no external environment needed. As your project grows, you can modularize routes or connect a real database, but this is the solid foundation many production-ready prototypes actually start from 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());
// Save or update a review for a specific product by a specific user
app.post("/api/reviews/:productId", async (req, res) => {
const { productId } = req.params;
const { userId, rating, comment } = req.body;
if (!userId || !rating) return res.status(400).send("Missing fields");
let reviews = (await db.get(`reviews_${productId}`)) || [];
const existing = reviews.find(r => r.userId === userId);
if (existing) {
existing.rating = rating;
existing.comment = comment;
existing.updatedAt = Date.now();
} else {
reviews.push({
userId,
rating,
comment,
createdAt: Date.now()
});
}
await db.set(`reviews_${productId}`, reviews);
const avgRating = (
reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length
).toFixed(2);
await db.set(`avg_${productId}`, avgRating);
res.send({ success: true, avgRating });
});
// Get product reviews with computed average
app.get("/api/reviews/:productId", async (req, res) => {
const { productId } = req.params;
const reviews = (await db.get(`reviews_${productId}`)) || [];
const avgRating = (await db.get(`avg_${productId}`)) || "0.00";
res.send({ avgRating, reviews });
});
app.listen(3000, () => console.log("Server running on port 3000"));
</script>
<script type="module">
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());
// This route syncs local reviews with an external sentiment analysis API
// to enrich user ratings with sentiment scores (useful for moderation or insights)
app.post("/api/reviews-with-sentiment/:productId", async (req, res) => {
const { productId } = req.params;
const { userId, rating, comment } = req.body;
if (!userId || !rating || !comment)
return res.status(400).json({ error: "Missing fields" });
try {
// Example of external API call (replace with your own API key stored in secrets)
const response = await fetch("https://api.meaningcloud.com/sentiment-2.1", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
key: process.env.SENTIMENT_API_KEY,
txt: comment,
lang: "en"
})
});
const sentimentData = await response.json();
const sentiment = sentimentData.score\_tag || "NONE";
const reviews = (await db.get(`reviews_${productId}`)) || [];
reviews.push({
userId,
rating,
comment,
sentiment,
createdAt: Date.now()
});
await db.set(`reviews_${productId}`, reviews);
res.json({
message: "Review saved with sentiment",
sentiment,
total: reviews.length
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "Failed to process sentiment" });
}
});
app.get("/api/reviews-with-sentiment/:productId", async (req, res) => {
const { productId } = req.params;
const reviews = (await db.get(`reviews_${productId}`)) || [];
res.json(reviews);
});
app.listen(3000, () => console.log("Server running on port 3000"));
</script>
<script type="module">
import express from "express";
import Database from "@replit/database";
const app = express();
const db = new Database();
app.use(express.json());
// Endpoint to fetch top-rated reviews for a product, filtered by a minimum number of ratings.
// Useful when you want to show only trusted user feedback on the frontend.
app.get("/api/reviews/top/:productId", async (req, res) => {
const { productId } = req.params;
const minRatings = parseInt(req.query.minRatings || "3", 10);
const reviews = (await db.get(`reviews_${productId}`)) || [];
// Count how many ratings each user has left across all products
const allKeys = await db.list("reviews\_");
const userRatingsCount = {};
for (const key of allKeys) {
const productReviews = (await db.get(key)) || [];
for (const r of productReviews) {
userRatingsCount[r.userId] = (userRatingsCount[r.userId] || 0) + 1;
}
}
// Filter by users who have given more than minRatings across the site
const filtered = reviews
.filter(r => userRatingsCount[r.userId] >= minRatings)
.sort((a, b) => b.rating - a.rating)
.slice(0, 5);
res.json({ topReviews: filtered });
});
app.listen(3000, () => console.log("Server listening 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.
A solid way to build a Reviews & Ratings feature on Replit is to use a simple Node.js (Express) backend with a lightweight database like SQLite or Replit Database (for simpler projects). The flow should include an API route to submit reviews, one to retrieve them, and basic validation before saving data. On the frontend (like React or plain HTML/JS), you’ll fetch from these endpoints. Remember to use Secrets for private info (like database paths if external), not hard-coded values. The backend code should go into a server.js file, and if using React, create components like ReviewForm.jsx and ReviewsList.jsx.
In your main Replit project, make sure you’ve selected the Node.js template. In the file sidebar, create a new file named server.js. This will handle your API routes.
// server.js
import express from "express";
import Database from "@replit/database";
import cors from "cors";
const app = express();
const db = new Database(); // Replit’s built-in key-value database
app.use(cors());
app.use(express.json());
// Route to submit a review
app.post("/api/reviews", async (req, res) => {
const { username, rating, comment } = req.body;
if (!username || !rating) {
return res.status(400).json({ error: "Username and rating are required." });
}
// Fetch existing reviews
let reviews = (await db.get("reviews")) || [];
// Add new review
const newReview = { username, rating, comment, date: new Date().toISOString() };
reviews.push(newReview);
// Save to Replit DB
await db.set("reviews", reviews);
res.status(201).json({ message: "Review added!", review: newReview });
});
// Route to get all reviews
app.get("/api/reviews", async (req, res) => {
const reviews = (await db.get("reviews")) || [];
res.json(reviews);
});
app.listen(3000, () => console.log("Server running on port 3000"));
Explanation: This code sets up a small HTTP API. You can send a POST request to /api/reviews to add a review or a GET request to retrieve them. Replit’s DB is persistent per Repl, so your data stays available.
Make sure in your Replit run command (the small “Run” button behavior), your main file is set to server.js in the .replit configuration if it’s not already.
If you’re using React, create a folder called components inside src, then two files named ReviewForm.jsx and ReviewsList.jsx.
// src/components/ReviewForm.jsx
import { useState } from "react";
export default function ReviewForm({ onSubmit }) {
const [username, setUsername] = useState("");
const [rating, setRating] = useState("");
const [comment, setComment] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
await onSubmit({ username, rating, comment });
setUsername("");
setRating("");
setComment("");
};
return (
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Your Name"
required
/>
<input
type="number"
min="1"
max="5"
value={rating}
onChange={(e) => setRating(e.target.value)}
placeholder="Rating 1-5"
required
/>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Your Review"
></textarea>
<button type="submit">Submit</button>
</form>
);
}
// src/components/ReviewsList.jsx
import { useEffect, useState } from "react";
export default function ReviewsList() {
const [reviews, setReviews] = useState([]);
useEffect(() => {
fetch("https://your-repl-name.username.repl.co/api/reviews")
.then(res => res.json())
.then(data => setReviews(data));
}, []);
return (
<div>
<h3>Reviews</h3>
{reviews.map((r, index) => (
<div key={index}>
<strong>{r.username}</strong> rated {r.rating}/5<br/>
<em>{r.comment}</em><br/>
<small>{new Date(r.date).toLocaleString()}</small>
<hr/>
</div>
))}
</div>
);
}
In your main React component (App.jsx), import these:
// src/App.jsx
import ReviewForm from "./components/ReviewForm";
import ReviewsList from "./components/ReviewsList";
export default function App() {
const handleSubmit = async (review) => {
await fetch("https://your-repl-name.username.repl.co/api/reviews", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(review),
});
};
return (
<div>
<ReviewForm onSubmit={handleSubmit} />
<ReviewsList />
</div>
);
}
Make sure to replace your-repl-name.username.repl.co with your actual Repl domain (you can see it in the browser preview URL).
/api/reviews inside the same repl if frontend and backend are served together.app.use(express.json()), your POST body won’t parse correctly.
This setup gives you a practical, realistic Reviews & Ratings system that’s light enough for Replit but structured well enough to scale later — all done in a way that matches how real apps behave inside the Replit environment.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.