Stripe Subscription Integration in Node.js [ltimate Guide]
Nov 22, 2024 pm 03:57 PMGetting 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.
Core Principle: Let Stripe Be the Source of Truth
The key is to shift as much of the logic to Stripe while keeping your database minimal. We only store:
- Customer ID
- Subscription ID
- Plan
This way, we avoid:
- Overcomplicated backend logic
- Error-prone webhook implementations for syncing dashboard changes
- Data redundancy
With this approach, you still have a fully functional subscription billing system while relying on Stripe as the single source of truth.
Features of Our Implementation
By the end of this guide, you’ll have a subscription-based app supporting:
- User subscription plans
- Checkout sessions
- Subscription upsells
- Available plan listing
Tech Stack
- PostgreSQL
- Node.js Express.js
- TypeScript
Step 1: Database Design
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:
- user_id: References your internal user table
- plan: Tracks the subscription plan
- subscription_id: The Stripe subscription ID
- is_owner: Flags the primary subscription holder
Step 2: Controllers
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:
- Custom subscription keys: Derived from the product name or lookup_key for clean plan checks (user.plan === 'pro_plan').
- Stripe-first approach: We fetch subscription data directly from Stripe, avoiding the “brain split.”
Step 3: Streamlined Stripe Checkout
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! };
Want to Skip All This?
We’ve packaged everything into a ready-to-go Open Source module. In less than 30 seconds, you can set up:
- Stripe integration
- Authentication
- Database configuration
- Prebuilt routes and SQL queries
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!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Java and JavaScript are different programming languages, each suitable for different application scenarios. Java is used for large enterprise and mobile application development, while JavaScript is mainly used for web page development.

JavaScriptcommentsareessentialformaintaining,reading,andguidingcodeexecution.1)Single-linecommentsareusedforquickexplanations.2)Multi-linecommentsexplaincomplexlogicorprovidedetaileddocumentation.3)Inlinecommentsclarifyspecificpartsofcode.Bestpractic

The following points should be noted when processing dates and time in JavaScript: 1. There are many ways to create Date objects. It is recommended to use ISO format strings to ensure compatibility; 2. Get and set time information can be obtained and set methods, and note that the month starts from 0; 3. Manually formatting dates requires strings, and third-party libraries can also be used; 4. It is recommended to use libraries that support time zones, such as Luxon. Mastering these key points can effectively avoid common mistakes.

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

JavaScriptispreferredforwebdevelopment,whileJavaisbetterforlarge-scalebackendsystemsandAndroidapps.1)JavaScriptexcelsincreatinginteractivewebexperienceswithitsdynamicnatureandDOMmanipulation.2)Javaoffersstrongtypingandobject-orientedfeatures,idealfor

Event capture and bubble are two stages of event propagation in DOM. Capture is from the top layer to the target element, and bubble is from the target element to the top layer. 1. Event capture is implemented by setting the useCapture parameter of addEventListener to true; 2. Event bubble is the default behavior, useCapture is set to false or omitted; 3. Event propagation can be used to prevent event propagation; 4. Event bubbling supports event delegation to improve dynamic content processing efficiency; 5. Capture can be used to intercept events in advance, such as logging or error processing. Understanding these two phases helps to accurately control the timing and how JavaScript responds to user operations.

JavaScripthassevenfundamentaldatatypes:number,string,boolean,undefined,null,object,andsymbol.1)Numbersuseadouble-precisionformat,usefulforwidevaluerangesbutbecautiouswithfloating-pointarithmetic.2)Stringsareimmutable,useefficientconcatenationmethodsf

If JavaScript applications load slowly and have poor performance, the problem is that the payload is too large. Solutions include: 1. Use code splitting (CodeSplitting), split the large bundle into multiple small files through React.lazy() or build tools, and load it as needed to reduce the first download; 2. Remove unused code (TreeShaking), use the ES6 module mechanism to clear "dead code" to ensure that the introduced libraries support this feature; 3. Compress and merge resource files, enable Gzip/Brotli and Terser to compress JS, reasonably merge files and optimize static resources; 4. Replace heavy-duty dependencies and choose lightweight libraries such as day.js and fetch
