Learn how to build a powerful booking platform using Replit with step-by-step guidance, coding tips, and best practices for smooth app development.

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 but real booking platform on Replit, create a Node.js backend using Express.js to handle bookings, a lightweight frontend with HTML/CSS/JS, and use Replit’s built-in SQLite database or Replit DB for storing booking data. You can manage environment secrets (like an admin password or API keys) through Replit’s built-in Secrets tab. This setup runs entirely inside Replit, so you can instantly test with your collaborators using Replit’s multiplayer feature. You’ll end up with a working prototype where users can create and view bookings, all deployed automatically when you press “Run”.
Start a new Repl using the “Node.js” template. Then create the following file structure:
In index.js, we’ll create a simple Express server with routes to save and fetch bookings. Run the commands in Replit Shell to install dependencies:
npm install express sqlite3 body-parser
// index.js
const express = require("express")
const bodyParser = require("body-parser")
const path = require("path")
const db = require("./database") // our local db helper
const app = express()
const PORT = process.env.PORT || 3000
app.use(bodyParser.json())
app.use(express.static(path.join(__dirname, "public")))
// API route to create a new booking
app.post("/api/bookings", async (req, res) => {
const { name, date, time } = req.body
if (!name || !date || !time) {
return res.status(400).json({ error: "Missing data" })
}
try {
await db.createBooking(name, date, time)
res.json({ message: "Booking created successfully" })
} catch (err) {
console.error(err)
res.status(500).json({ error: "Database error" })
}
})
// API route to get all bookings
app.get("/api/bookings", async (req, res) => {
try {
const bookings = await db.getBookings()
res.json(bookings)
} catch (err) {
console.error(err)
res.status(500).json({ error: "Database error" })
}
})
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
})
In this example, use SQLite (which works perfectly in Replit) to store booking data locally:
// database.js
const sqlite3 = require("sqlite3").verbose()
const db = new sqlite3.Database("./bookings.db")
// Initialize table if not exists
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS bookings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
date TEXT,
time TEXT
)
`)
})
function createBooking(name, date, time) {
return new Promise((resolve, reject) => {
db.run("INSERT INTO bookings (name, date, time) VALUES (?, ?, ?)", [name, date, time], function (err) {
if (err) reject(err)
else resolve(this.lastID)
})
})
}
function getBookings() {
return new Promise((resolve, reject) => {
db.all("SELECT * FROM bookings", (err, rows) => {
if (err) reject(err)
else resolve(rows)
})
})
}
module.exports = { createBooking, getBookings }
This simple UI lets users submit a booking and view existing ones.
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Booking Platform</title>
<style>
body { font-family: Arial; margin: 40px; }
form { margin-bottom: 20px; }
input, button { padding: 8px; margin: 5px; }
#bookings { margin-top: 30px; }
</style>
</head>
<body>
<h3>Create a Booking</h3>
<form id="bookingForm">
<input type="text" id="name" placeholder="Your Name" required />
<input type="date" id="date" required />
<input type="time" id="time" required />
<button type="submit">Book</button>
</form>
<h3>Current Bookings</h3>
<div id="bookings"></div>
<script>
async function loadBookings() {
const res = await fetch("/api/bookings")
const data = await res.json()
const container = document.getElementById("bookings")
container.innerHTML = data.map(b => `<p>${b.name} - ${b.date} at ${b.time}</p>`).join("")
}
document.getElementById("bookingForm").addEventListener("submit", async (e) => {
e.preventDefault()
const name = document.getElementById("name").value
const date = document.getElementById("date").value
const time = document.getElementById("time").value
await fetch("/api/bookings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, date, time })
})
e.target.reset()
loadBookings()
})
loadBookings()
</script>
</body>
</html>
If you later add integrations (like sending email confirmations), go to Replit’s left sidebar and open the Secrets tab. Add environment variables like
EMAIL_API_KEY there, and access them in Node.js via process.env.EMAIL_API_KEY.
process.env.PORT.
That’s the full, working path to get a real booking platform built and deployed inside Replit. It’s lightweight, 100% valid, and uses Replit’s true environment setup to run with database, backend, and frontend all inside one Repl.
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());
// Utility: build a composite key for quick lookups like "booking:userId:date"
const bookingKey = (userId, date) => `booking:${userId}:${date}`;
// POST /book — creates a booking if slot free
app.post("/book", async (req, res) => {
const { userId, date, timeSlot } = req.body;
const key = bookingKey(userId, date);
let bookings = (await db.get(key)) || [];
const alreadyBooked = bookings.find((b) => b.timeSlot === timeSlot);
if (alreadyBooked) {
return res.status(409).json({ msg: "Slot already booked" });
}
const newBooking = { timeSlot, createdAt: Date.now() };
bookings.push(newBooking);
await db.set(key, bookings);
res.json({ msg: "Booking confirmed", booking: newBooking });
});
// GET /bookings/:userId — fetch all bookings for a user
app.get("/bookings/:userId", async (req, res) => {
const { userId } = req.params;
const keys = await db.list(`booking:${userId}:`);
const data = await Promise.all(keys.map((k) => db.get(k)));
res.json(Object.fromEntries(keys.map((k, i) => [k, data[i]])));
});
app.listen(3000, () => console.log("Server running on port 3000"));
import express from "express";
import fetch from "node-fetch";
const router = express.Router();
// Example: connect Replit booking backend to Stripe for payments
router.post("/create-payment-intent", async (req, res) => {
const { amount, currency, userId } = req.body;
const stripeSecret = process.env.STRIPE_SECRET_KEY;
if (!stripeSecret) {
return res.status(500).json({ error: "Stripe API key not configured" });
}
try {
const response = await fetch("https://api.stripe.com/v1/payment\_intents", {
method: "POST",
headers: {
"Authorization": `Bearer ${stripeSecret}`,
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
amount: amount.toString(),
currency,
"metadata[userId]": userId
})
});
const paymentIntent = await response.json();
if (response.ok) {
res.json({ clientSecret: paymentIntent.client\_secret });
} else {
res.status(400).json(paymentIntent);
}
} catch (err) {
console.error("Stripe error:", err);
res.status(500).json({ error: "Internal Server Error" });
}
});
export default router;
// usage in index.js
// import paymentsRouter from "./payments.js"
// app.use("/api/payments", paymentsRouter);
import express from "express";
import Database from "@replit/database";
const router = express.Router();
const db = new Database();
// cancels a pending booking & safely restores slot availability (atomic handle)
router.post("/cancel", async (req, res) => {
const { userId, date, timeSlot } = req.body;
const key = `booking:${userId}:${date}`;
const currentBookings = (await db.get(key)) || [];
const updated = currentBookings.filter((b) => b.timeSlot !== timeSlot);
if (updated.length === currentBookings.length) {
return res.status(404).json({ msg: "Booking not found or already cancelled" });
}
await db.set(key, updated);
await db.delete(`slotlock:${date}:${timeSlot}`);
res.json({ msg: "Booking cancelled successfully", date, timeSlot });
});
export default router;
// usage in index.js
// import cancelRouter from "./cancelBooking.js"
// app.use("/api", cancelRouter);

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
When building a booking platform on Replit, focus on organizing code correctly, protecting secrets, and using Replit’s built-in tools the right way. Use Node.js with Express for your backend API, React for the frontend (Replit templates make this easy), and a hosted database like Replit DB, Supabase, or MongoDB Atlas for reliable data storage. Keep any credentials (like API keys) inside the Replit Secrets panel instead of hardcoding them. Finally, deploy straight from Replit with the "Deployments" tab, and don’t treat it exactly like local development — Replit containers can sleep, and file-system writes are temporary unless saved properly.
In your Replit project, use a structure like this:
booking-platform/
│
├── backend/
│ ├── server.js
│ ├── routes/
│ │ └── bookings.js
│ └── models/
│ └── bookingModel.js
│
├── frontend/
│ ├── src/
│ │ ├── App.js
│ │ └── components/
│ │ └── BookingForm.js
│ └── package.json
│
├── .replit
├── replit.nix
└── README.md
This keeps backend and frontend concerns clean. Your server.js runs on Replit when you press Run. The frontend can connect to it through a full URL like https://your-repl-name.username.repl.co/api.
Create a file at backend/server.js. This file will start a small Express server and handle routes for new bookings.
import express from "express"
import cors from "cors"
const app = express()
app.use(cors())
app.use(express.json()) // Allows you to read JSON in requests
// Example in-memory data store (for demo; replace with a real DB later)
let bookings = []
app.post("/api/bookings", (req, res) => {
const { name, email, date } = req.body
if (!name || !email || !date) {
return res.status(400).json({ message: "Missing required fields" })
}
const newBooking = { id: bookings.length + 1, name, email, date }
bookings.push(newBooking)
res.status(201).json(newBooking)
})
app.get("/api/bookings", (req, res) => {
res.json(bookings)
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
Place this in backend/server.js. In Replit, if your run command runs node backend/server.js, the server will start instantly when you press Run.
If you want data to persist, use a cloud DB. For a small project, Replit DB (key-value store) works fine. You can set it up quickly like this inside server.js:
import Database from "@replit/database"
const db = new Database()
// Save booking
await db.set(`booking_${newBooking.id}`, newBooking)
// Retrieve all bookings
let bookings = []
for await (const key of db.list()) {
const record = await db.get(key)
bookings.push(record)
}
Use this DB logic inside your API endpoints, replacing the in-memory bookings array above. Replit DB is automatically bound to your Repl and doesn’t need credentials.
Go to frontend/src/components/BookingForm.js and create a simple booking form. It will send data to the backend API hosted on the same Replit app.
import { useState } from "react"
export default function BookingForm() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [date, setDate] = useState("")
const handleSubmit = async (e) => {
e.preventDefault()
const response = await fetch("/api/bookings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, date })
})
const data = await response.json()
console.log("Booking saved:", data)
}
return (
<form onSubmit={handleSubmit}>
<input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
<input placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
<button type="submit">Book</button>
</form>
)
}
This file connects frontend input to your backend using fetch(). Run both the backend and React frontend Repls (or serve React build from Express when exporting to production).
If you connect an external database like MongoDB Atlas or use a payment API (Stripe, for example), never paste credentials into your code. In the Replit editor, click the Secrets icon (the lock symbol on the left sidebar), and add keys like DB_URI or STRIPE_KEY. Then in your backend:
const dbURI = process.env.DB_URI
// Example: connect using mongoose
import mongoose from "mongoose"
mongoose.connect(dbURI)
These variables are secure and won’t be committed to GitHub or shared with collaborators unless you explicitly share Secrets.
Replit makes full-stack building easy, but remember: treat it like a managed cloud sandbox, not a full VPS. For a booking platform, get your backend stable and tested first, then link your React frontend, and only after that, deploy. Use Replit’s simplicity for early MVPs — it’s perfect for startups or client demos, as long as you keep your API keys secret and your data layer external. This approach will give you a stable, real-world deployable booking platform fully manageable inside Replit.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.