/how-to-build-replit

How to Build a Subscription box service with Replit

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

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 Subscription box service with Replit

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.

 

Project Setup on Replit

 

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.

  • server.js → Main backend logic (Express app, routes, API endpoints)
  • public/index.html → Subscription form, fetched by Express static route
  • Replit Secrets → Store your Stripe keys or DB credentials safely here

 

// Inside the Shell on Replit
npm install express stripe replidb

 

Basic Express Setup

 

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!")
})

 

Create the Front-End Subscription Form

 

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>

 

Set Up Secrets in Replit

 

In Replit, open the sidebar and go to Tools → Secrets. Add:

  • KEY: STRIPE_SECRET_KEY
  • VALUE: your real Stripe secret key (starts with sk_live or sk_test)

Replit automatically injects it into process.env for your code.

 

Run and Test

 

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

 

Deployment and Notes

 

  • Persistence: Replit DB is persistent for small projects but not for production-scale. For scaling, connect MongoDB Atlas or Supabase.
  • Security: Never hardcode API keys. Always use Replit Secrets.
  • Collaboration: If working with teammates, use the “Invite” button so both can edit in real time.
  • Git Integration: Link your Repl with GitHub from the left panel for version control.
  • Custom Domain: Use Replit’s “Deployments” tab to set a custom domain when production-ready.

 

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.

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 Fetch Upcoming Subscription Boxes for Each User


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"));

How to Handle Stripe Webhooks and Create Shipments Automatically


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"));

How to Dynamically Estimate Shipping Costs for Your Subscription Box Service


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"));

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 Subscription box service with Replit

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.

 

Basic Structure

 

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 (Express app, payment handling setup).
  • routes/subscriptions.js: all subscription-related API routes.
  • client/: frontend files (HTML, CSS, JS, or React app if using React template).
  • .env or Secrets: store real API keys in Replit Secrets tab, never in code.

 

// 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}`)
})

 

Creating a Routes File

 

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

 

Connecting to a Database

 

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!" })
})

 

Secrets and Environment Variables

 

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:

  • STRIPE_SECRET_KEY: your real Stripe key (starts with sk\_).
  • DATABASE\_URL: if using external DB.

Then access them in code as environment variables:

 

const stripeSecret = process.env.STRIPE_SECRET_KEY

 

Integrating Payment (Stripe Example)

 

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 })
  }
})

 

Frontend Setup

 

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>

 

Replit Specific Tips

 

  • Don’t run migrations or heavy scripts inside Replit shell; use light scripts or external dashboards.
  • Secrets are per Repl — if you fork or invite collaborators, be explicit about who can see them.
  • Use the “Deploy” button for a stable, always-on service; otherwise, Replit free-tier apps sleep after inactivity.
  • Use console.log() generously for debugging, since Replit shows both backend and frontend logs live.
  • Back up your code to GitHub often using built-in Git integration in Replit.

 

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.

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