Learn how to create and launch a successful subscription box service using Replit. Step-by-step guide for beginners and entrepreneurs.

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 subscription box service in Replit, you’ll create a small web app (using Node.js and Express) that lets users subscribe, stores their subscription data in a database, and integrates a payment system like Stripe. In Replit, you can host this directly, set environment variables for API keys using the “Secrets” tab, and even sync your code with GitHub for version control. The flow is: serve the front-end (HTML + basic CSS), handle subscription form submission, securely process payments via Stripe API, then store subscriber info in a database such as Replit DB (built-in option) or an external database like MongoDB Atlas.
Choose the "Node.js" template when creating your new Repl. This gives you a server.js file by default. You’ll keep your back-end logic there. Create a public folder for your front-end HTML/CSS, and optionally a views folder if you prefer templating later.
// Inside the Shell on Replit
npm install express stripe replidb
Open server.js and replace everything with this code:
// server.js
const express = require("express")
const path = require("path")
const bodyParser = require("body-parser")
const Stripe = require("stripe")
const { Database } = require("replidb") // Using built-in Replit DB wrapper
const app = express()
const db = new Database()
// This key should be stored in Replit Secrets (Tools > Secrets)
const stripe = Stripe(process.env.STRIPE_SECRET_KEY)
app.use(bodyParser.json())
app.use(express.static(path.join(__dirname, "public")))
// Route to handle subscription
app.post("/subscribe", async (req, res) => {
const { email, name, plan } = req.body
try {
// Create customer in Stripe
const customer = await stripe.customers.create({ email, name })
// Example subscription creation
const priceId = "price_xxx" // Replace with your Stripe price ID
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: priceId }]
})
// Save subscriber info to Replit DB
await db.set(email, { name, plan, stripeId: subscription.id })
res.json({ success: true, message: "Subscription successful!" })
} catch (error) {
console.error(error)
res.status(500).json({ success: false, message: "Subscription failed" })
}
})
// Replit automatically assigns PORT
app.listen(process.env.PORT || 3000, () => {
console.log("Server running!")
})
In the left sidebar, create a folder named public and inside it, a file called index.html. Add a simple form that posts to your backend.
<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>My Subscription Box</title>
<style>
body { font-family: sans-serif; max-width: 500px; margin: 50px auto; }
input, select, button { display: block; width: 100%; margin-top: 10px; padding: 10px; }
</style>
</head>
<body>
<h2>Join Our Subscription Box!</h2>
<form id="subForm">
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="Your email" required />
<select name="plan">
<option value="basic">Basic</option>
<option value="premium">Premium</option>
</select>
<button type="submit">Subscribe</button>
</form>
<script>
const subForm = document.getElementById("subForm")
subForm.addEventListener("submit", async (e) => {
e.preventDefault()
const formData = Object.fromEntries(new FormData(subForm))
const response = await fetch("/subscribe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData)
})
const result = await response.json()
alert(result.message)
})
</script>
</body>
</html>
In Replit, open the sidebar and go to Tools → Secrets. Add:
Replit automatically injects it into process.env for your code.
Click the green Run button in Replit. Then open the webview tab. Fill the form — your request will hit the backend, Stripe will process it (if keys are real), and you’ll see saved data in your Replit DB. You can check the DB with:
// In the Shell
await db.list() // see all emails
await db.get("[email protected]") // see one record
This structure works end-to-end inside Replit — you can build your subscription box MVP, process payments, and keep data securely, all without leaving the platform.
const express = require("express");
const bodyParser = require("body-parser");
const Database = require("@replit/database");
const db = new Database();
const app = express();
app.use(bodyParser.json());
// Helper to group items into upcoming subscription boxes per user
async function getUserBoxes(userId) {
const allBoxes = (await db.get(`boxes:${userId}`)) || [];
const subscriptions = (await db.get(`subs:${userId}`)) || [];
const active = subscriptions.filter(s => s.status === "active");
const nextBoxes = allBoxes.filter(b =>
new Date(b.shipDate) >= new Date() &&
active.some(s => s.planId === b.planId)
);
return nextBoxes.map(b => ({
boxId: b.id,
plan: active.find(s => s.planId === b.planId)?.planName,
shipDate: b.shipDate,
items: b.items
}));
}
// API endpoint for frontend dashboard to fetch upcoming shipments
app.get("/api/user/:id/next-boxes", async (req, res) => {
try {
const boxes = await getUserBoxes(req.params.id);
res.json({ boxes });
} catch (err) {
console.error("Error fetching boxes", err);
res.status(500).json({ error: "Failed to fetch subscription boxes" });
}
});
app.listen(3000, () => console.log("✅ Server running on port 3000"));
const express = require("express");
const fetch = require("node-fetch");
require("dotenv").config();
const app = express();
app.use(express.json());
// Handle webhook from Stripe after successful subscription payment
app.post("/webhook/stripe", async (req, res) => {
const event = req.body;
if (event.type === "checkout.session.completed") {
const session = event.data.object;
// Call your fulfillment endpoint or external shipping API (e.g., Shippo)
const response = await fetch("https://api.goshippo.com/shipments/", {
method: "POST",
headers: {
"Authorization": `ShippoToken ${process.env.SHIPPO_API_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
address\_from: { city: "New York", state: "NY", country: "US" },
address_to: { name: session.customer_details.name, city: session.customer_details.address.city, state: session.customer_details.address.state, country: session.customer\_details.address.country },
parcels: [{ length: "10", width: "7", height: "4", distance_unit: "in", weight: "2", mass_unit: "lb" }]
})
});
const shipment = await response.json();
console.log("Shipment created for order:", shipment.object\_id);
}
res.status(200).json({ received: true });
});
app.listen(3000, () => console.log("🚀 Webhook listener running on port 3000"));
const express = require("express");
const Database = require("@replit/database");
const fetch = require("node-fetch");
require("dotenv").config();
const db = new Database();
const app = express();
app.use(express.json());
// Endpoint to calculate shipping cost dynamically before checkout
app.post("/api/shipping/estimate", async (req, res) => {
const { userId, items } = req.body;
try {
const user = await db.get(`user:${userId}`);
if (!user || !user.address) return res.status(400).json({ error: "Missing user address" });
const weight = items.reduce((total, i) => total + i.weight \* i.quantity, 0);
const response = await fetch("https://api.goshippo.com/shipments/", {
method: "POST",
headers: {
"Authorization": `ShippoToken ${process.env.SHIPPO_API_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
address\_from: { city: "New York", state: "NY", country: "US" },
address\_to: {
city: user.address.city,
state: user.address.state,
country: user.address.country
},
parcels: [{ length: "10", width: "7", height: "4", distance_unit: "in", weight, mass_unit: "lb" }]
})
});
const data = await response.json();
const rate = data.rates?.[0]?.amount || "0.00";
res.json({ estimatedShipping: parseFloat(rate).toFixed(2) });
} catch (err) {
console.error("Shipping estimate failed:", err);
res.status(500).json({ error: "Failed to estimate shipping" });
}
});
app.listen(3000, () => console.log("📦 Shipping estimation API running"));

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 subscription box service on Replit, treat Replit as a real production-like environment but lighter, built for iteration and demos. You’ll build a small but complete Node.js + Express backend, connect it to a Replit-hosted database (or external DB like Supabase), securely manage API keys in Replit Secrets, and host a simple React or HTML/JS frontend. The main best practice is to keep backend and frontend structured clearly, avoid committing secret keys, and use Replit’s built-in web hosting and deployment features correctly instead of assuming it behaves like your local Docker or AWS server.
Start a new Replit Node.js project. Replit will automatically create index.js as your main entry file. Create a folder named client/ for frontend files and a folder named routes/ for backend routes. This keeps the project tidy and makes it similar to production setups.
// index.js - main server entry
import express from "express"
import bodyParser from "body-parser"
import cors from "cors"
import { config } from "dotenv"
import subscriptionRoutes from "./routes/subscriptions.js"
config() // Loads environment variables if local; Replit Secrets auto-available
const app = express()
app.use(cors())
app.use(bodyParser.json())
// Mount routes
app.use("/api/subscriptions", subscriptionRoutes)
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
In Replit, click the “New File” button and create routes/subscriptions.js. This file defines your backend endpoints responsible for handling new subscriptions, fetching existing customers, and connecting to payment services like Stripe.
// routes/subscriptions.js
import express from "express"
const router = express.Router()
// Mock database for demo - replace with real DB later
const subscribers = []
// POST /api/subscriptions/new
router.post("/new", (req, res) => {
const { name, email, plan } = req.body
if (!email || !plan) {
return res.status(400).json({ error: "Email and plan are required" })
}
subscribers.push({ name, email, plan })
res.json({ success: true, message: "Subscription created!" })
})
export default router
Replit includes a small database feature called Replit Database, good for small projects. For production-ready apps, use Supabase, MongoDB Atlas, or Firebase. To use Replit DB, add this near the top of routes/subscriptions.js:
// Using Replit Database
import Database from "@replit/database"
const db = new Database()
router.post("/new", async (req, res) => {
const { name, email, plan } = req.body
if (!email || !plan) {
return res.status(400).json({ error: "Email and plan required" })
}
await db.set(email, { name, plan })
res.json({ message: "Subscription stored in database!" })
})
Instead of typing any payment or database keys into your code, store them in the Replit Secrets tab (the padlock icon on the left sidebar). For example, add:
sk\_).Then access them in code as environment variables:
const stripeSecret = process.env.STRIPE_SECRET_KEY
Install Stripe with Replit’s Package Manager (left sidebar → Search “stripe”). Then, add a simple route for checkout. Put this inside routes/subscriptions.js below other route definitions.
import Stripe from "stripe"
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
router.post("/checkout", async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "subscription",
line_items: [
{
price: "price_1234", // Replace with your Stripe price ID
quantity: 1
}
],
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/cancel`,
})
res.json({ url: session.url })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
For a minimal HTML/JS frontend, create client/index.html and link it to your Express backend using Replit’s hosting domain (for example, https://your-repl-name.your-username.repl.co/api/subscriptions/).
<!-- client/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Subscription Box Signup</title>
</head>
<body>
<h1>Subscribe to Our Box!</h1>
<form id="subForm">
<input type="text" placeholder="Name" id="name" required />
<input type="email" placeholder="Email" id="email" required />
<select id="plan">
<option value="basic">Basic</option>
<option value="premium">Premium</option>
</select>
<button type="submit">Subscribe</button>
</form>
<script>
document.getElementById("subForm").addEventListener("submit", async (e) => {
e.preventDefault()
const res = await fetch("/api/subscriptions/new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: document.getElementById("name").value,
email: document.getElementById("email").value,
plan: document.getElementById("plan").value
})
})
const data = await res.json()
alert(data.message || "Subscribed!")
})
</script>
</body>
</html>
Following this pattern gives you a clean separation of concerns, keeps your secrets safe, and ensures the subscription box app runs reliably in Replit’s environment.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.