Getting Stripe subscriptions working with backend services can be tricky and often leads to what developers call the dreaded “brain split” - managing both Stripe's logic and your own backend data in sync.
At Vratix, we’ve tackled this problem head-on while building our Open Source Stripe Subscriptions API Module. Here's how we approach Stripe subscription billing in Node.js to keep things simple, scalable, and developer-friendly.
The key is to shift as much of the logic to Stripe while keeping your database minimal. We only store:
This way, we avoid:
With this approach, you still have a fully functional subscription billing system while relying on Stripe as the single source of truth.
By the end of this guide, you’ll have a subscription-based app supporting:
We start by designing a clean, minimal database table:
CREATE TABLE user_subscriptions ( "id" SERIAL PRIMARY KEY, "plan" VARCHAR NOT NULL, "user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, "customer_id" VARCHAR, "subscription_id" VARCHAR NOT NULL, "is_owner" BOOLEAN NOT NULL DEFAULT TRUE, "created_at" TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE (user_id, subscription_id) );
Key points:
We use a factory function to keep the business logic modular and testable. Here's a snippet from our Stripe Subscription Controller:
async getSubscriptions() { const stripePrices = await stripe.prices.list({ active: true, type: "recurring", expand: ["data.product"], }); return stripePrices.data.map((price) => { const product = price.product as Stripe.Product; return { plan: price.lookup_key || product.name.toLowerCase().replaceAll(" ", "_"), name: product.name, priceId: price.id, interval: price.recurring!.interval, price: { currency: price.currency, amount: price.unit_amount }, }; }); }
Key highlights:
Our createCheckout function sets up a subscription checkout session:
const checkout = await stripe.checkout.sessions.create({ line_items: [ { price: priceId, adjustable_quantity: { enabled: true }, quantity: seats || 1, }, ], mode: "subscription", subscription_data: { metadata: { userId } }, success_url: CHECKOUT_SUCCESS_URL, cancel_url: CHECKOUT_CANCEL_URL, }); return { url: checkout.url! };
We’ve packaged everything into a ready-to-go Open Source module. In less than 30 seconds, you can set up:
Run this:
CREATE TABLE user_subscriptions ( "id" SERIAL PRIMARY KEY, "plan" VARCHAR NOT NULL, "user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, "customer_id" VARCHAR, "subscription_id" VARCHAR NOT NULL, "is_owner" BOOLEAN NOT NULL DEFAULT TRUE, "created_at" TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE (user_id, subscription_id) );
Check out our Stripe Subscriptions Module Docs for more details.
The full code is available on our GitHub repo.
See a demo video how to do all of this with a working UI here.
I’d love to hear your thoughts - does this make building subscription APIs easier? Let us know what features you’d like to see next!
The above is the detailed content of Stripe Subscription Integration in Node.js [ltimate Guide]. For more information, please follow other related articles on the PHP Chinese website!