Learn how to build a ride-hailing platform using Replit. Follow our step-by-step guide to create and launch your own successful app 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.
You can absolutely build a ride-hailing prototype on Replit by combining a backend (Node.js + Express) and a frontend (React or simple HTML/JS). You’ll use Replit’s built-in web server as the backend host, store your credentials in Replit Secrets, and optionally connect MongoDB Atlas for persistent data. The main idea: the driver and passenger apps talk to the same backend through REST APIs — drivers send their location, passengers request rides, and the server matches them. Replit is great for getting this MVP working end-to-end, but not meant for handling real-time scale like Uber. Still, for a working demo with authentication, ride requests, and a map integration (via Google Maps API), it works beautifully when you build it step by step.
Start a new Node.js Repl (not HTML/CSS/JS, since you need a backend). Once created, you’ll have a main file named index.js. We’ll add folders for clarity:
// Inside the Replit shell, run this to create folders
mkdir backend frontend
Open index.js (the main file) and replace the content with this code:
// index.js
import express from "express"
import cors from "cors"
const app = express()
const port = process.env.PORT || 3000
app.use(cors()) // Allow frontend to call backend
app.use(express.json()) // Parse JSON bodies
// Simple test route
app.get("/", (req, res) => {
res.send("Ride-hailing backend is running!")
})
// Import routes for riders and drivers
import "./backend/rides.js"
app.listen(port, () => {
console.log(`Server is listening on port ${port}`)
})
This starts your backend. Replit will automatically expose it through a public URL shown at the top ("Open in a new tab").
Create a new file backend/rides.js. This file tracks ride requests temporarily in memory (in real usage, you’d connect a database).
// backend/rides.js
import express from "express"
import { Router } from "express"
const router = Router()
// Temporary store for demo purposes
let rides = []
let drivers = []
// Passenger requests a ride
router.post("/request", (req, res) => {
const { passengerName, pickup } = req.body
const ride = { id: Date.now(), passengerName, pickup, status: "waiting" }
rides.push(ride)
res.json({ message: "Ride requested", ride })
})
// Driver updates location or marks availability
router.post("/driver/update", (req, res) => {
const { driverName, location } = req.body
const existing = drivers.find(d => d.name === driverName)
if (existing) {
existing.location = location
} else {
drivers.push({ name: driverName, location })
}
res.json({ message: "Driver location updated" })
})
// List available rides
router.get("/rides", (req, res) => {
res.json(rides)
})
export default router
Make sure in index.js, you import this route file after setting up Express. In Replit, ES modules are allowed, so your type in package.json should be "module":
{
"name": "ride-hailing",
"type": "module",
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
}
}
If you don’t want React, a simple frontend/index.html works fine to test the API:
<!-- frontend/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Ride App</title>
</head>
<body>
<h1>Request a Ride</h1>
<input id="name" placeholder="Your Name"/>
<input id="pickup" placeholder="Pickup Location"/>
<button onclick="requestRide()">Request</button>
<pre id="output"></pre>
<script>
async function requestRide() {
const name = document.getElementById("name").value
const pickup = document.getElementById("pickup").value
const res = await fetch("/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ passengerName: name, pickup })
})
const data = await res.json()
document.getElementById("output").textContent = JSON.stringify(data, null, 2)
}
</script>
</body>
</html>
In Replit, you can serve this static file easily by adding another route inside index.js before app.listen():
app.use(express.static("frontend"))
For sensitive info like Google Maps API keys or MongoDB credentials, never hardcode them. Instead, in Replit, click the Lock icon (Secrets tab), add keys like MAPS_API_KEY, and read them from process.env.MAPS_API_KEY in your code.
To make rides persist beyond server restart, use MongoDB Atlas. In Replit Shell, install the package:
npm install mongoose
Then in backend/rides.js you can connect at top of file:
import mongoose from "mongoose"
mongoose.connect(process.env.MONGO_URI)
const RideSchema = new mongoose.Schema({
passengerName: String,
pickup: String,
status: String
})
const Ride = mongoose.model("Ride", RideSchema)
Replit automatically deploys your app: as soon as it runs, it’s live on your Replit URL. You can share it with teammates by inviting them using the “Invite” or “Collaborate” button — they can code simultaneously in real time. Keep an eye on Replit’s Shell logs for debugging; remember that sleeping Repls may stop after inactivity unless you’re on a paid plan.
A ride-hailing platform built like this on Replit can handle small-scale demos, internal tests, or prototypes. It’s excellent for learning full-stack interactions and quickly iterating on functionality. For production-level scaling, you’d later migrate the backend to a proper hosting service and link mobile apps. But for now, with these steps, you’ll have a working, interactive ride-hailing demo that actually runs from within Replit end to end.
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const { v4: uuid } = require('uuid');
app.use(bodyParser.json());
// Simple in-memory data store for demo
let drivers = {};
let rideRequests = {};
// Register driver with realtime location
app.post('/drivers/register', (req, res) => {
const { name, lat, lng } = req.body;
const id = uuid();
drivers[id] = { id, name, lat, lng, available: true };
res.json({ driverId: id });
});
// Update driver location
app.post('/drivers/:id/location', (req, res) => {
const { id } = req.params;
const { lat, lng } = req.body;
if (!drivers[id]) return res.status(404).json({ error: 'Driver not found' });
drivers[id].lat = lat;
drivers[id].lng = lng;
res.json({ status: 'Location updated' });
});
// Customer requests a ride
app.post('/rides/request', (req, res) => {
const { customerName, pickup, destination } = req.body;
const id = uuid();
// Find nearest available driver (simple distance check)
let nearestDriver = null;
let minDistance = Infinity;
for (const d of Object.values(drivers)) {
if (!d.available) continue;
const dist = Math.hypot(d.lat - pickup.lat, d.lng - pickup.lng);
if (dist < minDistance) {
minDistance = dist;
nearestDriver = d;
}
}
if (!nearestDriver) return res.status(404).json({ error: 'No drivers available' });
nearestDriver.available = false;
rideRequests[id] = {
id,
customerName,
pickup,
destination,
driver: nearestDriver,
status: 'assigned'
};
res.json({ rideId: id, driver: nearestDriver });
});
// Complete ride
app.post('/rides/:id/complete', (req, res) => {
const { id } = req.params;
const ride = rideRequests[id];
if (!ride) return res.status(404).json({ error: 'Ride not found' });
ride.status = 'completed';
drivers[ride.driver.id].available = true;
res.json({ message: 'Ride completed', ride });
});
app.listen(3000, () => {
console.log('Ride backend API running on port 3000');
});
import express from "express";
import fetch from "node-fetch";
const app = express();
app.use(express.json());
// Example: calculate distance & ETA using Google Maps Distance Matrix API
app.post("/api/route-info", async (req, res) => {
const { pickup, destination } = req.body;
if (!pickup || !destination)
return res.status(400).json({ error: "Missing pickup or destination." });
try {
const apiKey = process.env.MAPS_API_KEY; // store this in Replit Secrets
const params = new URLSearchParams({
origins: `${pickup.lat},${pickup.lng}`,
destinations: `${destination.lat},${destination.lng}`,
key: apiKey,
});
const response = await fetch(`https://maps.googleapis.com/maps/api/distancematrix/json?${params}`);
const data = await response.json();
if (data.rows?.[0]?.elements?.[0]?.status !== "OK") {
return res.status(400).json({ error: "Unable to fetch route data." });
}
const info = data.rows[0].elements[0];
res.json({
distance: info.distance.text,
duration: info.duration.text,
valueMeters: info.distance.value,
valueSeconds: info.duration.value,
});
} catch (err) {
console.error("Route Info Error:", err);
res.status(500).json({ error: "Failed to fetch route info." });
}
});
app.listen(3000, () => console.log("Route API running on port 3000"));
import express from "express";
import Database from "@replit/database";
import jwt from "jsonwebtoken";
const app = express();
const db = new Database();
app.use(express.json());
const SECRET = process.env.JWT\_SECRET;
// Middleware for verifying driver tokens
const authDriver = async (req, res, next) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "No token" });
try {
const payload = jwt.verify(token, SECRET);
req.driver = payload;
next();
} catch {
res.status(403).json({ error: "Invalid token" });
}
};
// Secure endpoint for drivers to update current status and location
app.post("/driver/update", authDriver, async (req, res) => {
const { lat, lng, available } = req.body;
const id = req.driver.id;
if (typeof lat !== "number" || typeof lng !== "number")
return res.status(400).json({ error: "Invalid coordinates" });
await db.set(`driver_${id}`, { lat, lng, available });
res.json({ message: "Driver status updated" });
});
// Secure customer endpoint to find nearby available drivers from Replit DB
app.post("/customer/search", async (req, res) => {
const { pickup } = req.body;
if (!pickup || typeof pickup.lat !== "number" || typeof pickup.lng !== "number")
return res.status(400).json({ error: "Invalid pickup" });
const allKeys = await db.list("driver\_");
const drivers = await Promise.all(allKeys.map((k) => db.get(k)));
const available = drivers.filter((d) => d?.available);
const nearest = available.sort(
(a, b) =>
Math.hypot(a.lat - pickup.lat, a.lng - pickup.lng) -
Math.hypot(b.lat - pickup.lat, b.lng - pickup.lng)
)[0];
if (!nearest)
return res.status(404).json({ error: "No nearby drivers" });
res.json({ driver: nearest });
});
app.listen(3000, () => console.log("Secure driver/customer API running on Replit"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
On Replit, the best way to build a ride-hailing platform is to separate your front-end (for users and drivers) and back-end (for routes, payments, real-time updates) inside one Repl using a clear folder structure. Use Node.js with Express for your server-side logic and a simple React or HTML/CSS/JS front-end. Replit provides a single running container, so you must design it efficiently: avoid running multiple servers, store credentials in Secrets, and rely on persistent storage through a database like PostgreSQL (via an external provider, e.g. Supabase). WebSockets (using Socket.IO) help you show live driver location updates. Use Replit’s built-in web server preview during development, and once stable, connect it to your custom domain through Replit Deployments or an external reverse proxy.
In Replit, create a new Node.js Repl. This gives you an index.js file and a package.json file. Organize your folders like this:
// Sample folder structure
.
├── client/
│ └── index.html
│ └── main.js
│
├── server/
│ └── server.js
│ └── routes.js
│
├── package.json
└── .replit
Inside server/server.js, create your main server that handles requests from riders and drivers. This server also broadcasts live updates like driver locations.
// server/server.js
import express from "express"
import { createServer } from "http"
import { Server } from "socket.io"
import cors from "cors"
const app = express()
app.use(cors())
app.use(express.json())
// Basic Rider API Example
app.post("/api/ride-request", (req, res) => {
// req.body contains pickup location, destination etc.
console.log("Ride requested:", req.body)
res.json({ status: "Ride request received" })
})
// Create HTTP + WebSocket Server
const server = createServer(app)
const io = new Server(server, {
cors: { origin: "*" }
})
// Listen for live location updates
io.on("connection", (socket) => {
console.log("User connected:", socket.id)
socket.on("driver-location", (data) => {
// Broadcast driver location to waiting rider clients
io.emit("update-location", data)
})
socket.on("disconnect", () => {
console.log("User disconnected")
})
})
const PORT = process.env.PORT || 3000
server.listen(PORT, () => console.log(`Server running on port ${PORT}`))
Where to put this: Create server/server.js and paste the code above. It runs both Express and Socket.IO within one process — ideal for Replit since one Repl gets one main port.
Inside client/index.html and client/main.js, connect to your backend using Socket.IO to show real-time driver updates. You can serve static files directly from your Express app too.
<!-- client/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Ride Hailing Demo</title>
</head>
<body>
<h1>Rider Screen</h1>
<div id="map"></div>
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<script src="./main.js"></script>
</body>
</html>
// client/main.js
const socket = io(window.location.origin)
socket.on("update-location", (loc) => {
console.log("Driver location update:", loc)
})
// Example: simulate driver location updates every few seconds
setInterval(() => {
socket.emit("driver-location", {
lat: (Math.random() * 90).toFixed(4),
lng: (Math.random() * 180).toFixed(4)
})
}, 3000)
Where to put this: In your client folder. You can also use React by initializing a React app inside client and ensuring Express serves it via app.use(express.static("client/build")) after build.
All sensitive data (like database connection strings or API tokens for payment gateways such as Stripe) should go into the Secrets tab in Replit (lock icon on left sidebar). Access them inside your server via process.env.VAR\_NAME.
// Example usage in server/server.js
const db_url = process.env.DB_URL
Replit storage isn’t for production-grade databases. So, connect to an external provider like Supabase or Neon.tech. Add your connection string in Secrets as DB\_URL and then use it with a driver like `pg`.
// server/db.js
import pkg from "pg"
const { Pool } = pkg
const pool = new Pool({
connectionString: process.env.DB_URL,
ssl: { rejectUnauthorized: false }
})
export default pool
Then use it in your route file:
// server/routes.js
import express from "express"
import pool from "./db.js"
const router = express.Router()
router.get("/drivers", async (req, res) => {
const { rows } = await pool.query("SELECT * FROM drivers")
res.json(rows)
})
export default router
In your .replit file, make sure you start your server from the correct file path:
run = "node server/server.js"
Then click “Run” in Replit — it will host your app and give you a web URL to preview. When testing Socket.IO or APIs, open the Replit web view link directly in your browser.
For production, either use Replit Deployments (Always On) or export your code to an external host (Render, Fly.io, etc.). Replit Deployments keep your server running, but for scale, external hosting is often cheaper per connection if you expect many drivers and riders.
Following this structure, you’ll get a clean, maintainable ride-hailing platform prototype: an Express API, Socket.IO-powered live updates, and a front-end all running nicely inside Replit’s limitations while still keeping realistic room for scaling later.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.