by Aman Sh.

Revstack is an open-source billing platform that handles subscriptions, entitlements, usage metering, and payment provider abstraction. Define pricing models as code, deploy via CLI, and gate features in milliseconds across Node, Next.js, and React.
Revstack is a self-hostable billing engine for SaaS products. It handles the full subscription lifecycle: entitlements, usage metering, payment provider abstraction, and webhook normalization. The typical buyer is a backend/fullstack team that wants to drop in a production-ready billing API rather than build one from scratch.
apps/api/ - The core Hono-based REST API server (billing engine, all routes, DI container)apps/checkout/ - Checkout flow applicationpackages/ - Client SDKs and shared packages (node, next, react, browser, auth, ai, cli)scripts/ - Workspace utility scripts.agents/ - AI agent rules and skill definitions for code generation.changeset/ - Changeset versioning configuration.vscode/ - Shared editor settingsturbo.json - Turborepo build pipeline configurationpnpm-workspace.yaml - Monorepo workspace definitionpackage.json - Root package manifest with workspacesnpm install @hono/zod-openapi @hono/node-server hono zod
npm install drizzle-orm postgres
npm install jose
npm install -D typescript tsx @types/node vitest tsup turbo
This monorepo uses pnpm as its package manager. Install pnpm globally first if you are adopting the full source tree:
npm install -g pnpm pnpm installNo native build steps (no pods, no Android linking) are required. Node.js 20+ is expected.
Copy the source into your project root or a subdirectory (e.g., ./billing/):
your-project/
├── billing/ ← drop source/ here
│ ├── apps/api/
│ ├── packages/
│ └── ...
└── your-app/
Install dependencies from the monorepo root:
cd billing
pnpm install
Configure environment variables. Create apps/api/.env:
Spin up an isolated sandbox and run it server-side — no local setup.
Tetrees AI Review of this version
This TypeScript cli / script completed archive review with strong static results. Structure, dependency manifests, documentation, functional source, and common risk patterns were checked by the Tetrees verification pipeline; runtime phases are stated separately.
Deterministic AVCP artifact review
Pipeline avcp-2026-08-04.1 · SHA-256 cadb414fbdbfc948…
This version-scoped review deterministically inspects the submitted archive for structure, dependencies, documentation, functional source, and common malicious or high-risk signals. Build and test phases are reported as passed only after an isolated sandbox audition. It is not a guarantee of perfect security.
Reviewed Aug 4, 2026
Push this product straight into your AI IDE, web builder or cloud IDE.
Connect Tetrees to a compatible AI IDE, list products you own, and request the verified ZIP without exposing seller upload controls.
No reviews yet.
Sign in to join the discussion
Loading discussion…
DATABASE_URL=postgres://user:password@localhost:5432/revstack
JWT_SECRET=your-jwt-secret-256-bit
PORT=3000
NODE_ENV=development
Wire TypeScript paths. The API uses @/ as a path alias. Confirm apps/api/tsconfig.json contains:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": { "@/*": ["./*"] }
}
}
Run the API server:
cd apps/api
pnpm dev
Build for production:
pnpm build
node dist/index.js
Mount as a sub-app (optional, if embedding in an existing Express/Hono server): use the fetch handler from @hono/node-server to proxy or mount the app object exported from apps/api/src/index.ts.
buildContainerimport { buildContainer, type AppEnv } from "@/container";
const container: ReturnType<typeof buildContainer> = buildContainer();
Constructs and returns the dependency injection container holding all service singletons (repositories, use-cases, cache, event bus). Call once at startup and inject results into request context via c.set().
requireAuthimport { requireAuth } from "@/common/middlewares/require-auth";
app.use("/v1/*", requireAuth);
Hono middleware that validates the incoming JWT on every request under /v1/*. Attach it before mounting any route that should be protected. Authentication failure results in a structured error response before the handler executes.
globalErrorHandlerimport { globalErrorHandler } from "@/common/middlewares/error-handler";
app.onError(globalErrorHandler);
Registered as Hono's onError hook. Catches unhandled errors from any route or middleware and serializes them into a consistent JSON error envelope. Register this before any routes to ensure all errors are normalized.
addonsRoutesimport { addonsRoutes } from "@/modules/addons/infrastructure/http";
app.route("/v1/addons", addonsRoutes);
Pre-built OpenAPI-annotated Hono router for the addons module. Each module (couponsRoutes, subscriptionsRoutes, invoicesRoutes, etc.) follows the same pattern and is mounted the same way.
Drop the API into a Node.js process and serve it on a custom port. This is the standard self-hosted path.
// server.ts (at apps/api/src/index.ts level)
import { OpenAPIHono } from "@hono/zod-openapi";
import { serve } from "@hono/node-server";
import { buildContainer, type AppEnv } from "@/container";
import { globalErrorHandler } from "@/common/middlewares/error-handler";
import { requireAuth } from "@/common/middlewares/require-auth";
import { addonsRoutes } from "@/modules/addons/infrastructure/http";
import { subscriptionsRoutes } from "@/modules/subscriptions/infrastructure/http";
import { customersRoutes } from "@/modules/customers/infrastructure/http";
const app = new OpenAPIHono<AppEnv>();
app.onError(globalErrorHandler);
const container = buildContainer();
app.use("*", async (c, next) => {
for (const [key, value] of Object.entries(container)) {
c.set(key as keyof typeof container, value);
}
await next();
});
app.use("/v1/*", requireAuth);
app
.route("/v1/addons", addonsRoutes)
.route("/v1/subscriptions", subscriptionsRoutes)
.route("/v1/customers", customersRoutes);
serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) }, (info) => {
console.log(`Revstack API listening on http://localhost:${info.port}`);
});
If you already have a Hono application, embed the billing engine as a sub-router without running a separate process.
// existing-app.ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { serve } from "@hono/node-server";
import { buildContainer, type AppEnv } from "./billing/apps/api/src/container";
import { globalErrorHandler } from "./billing/apps/api/src/common/middlewares/error-handler";
import { requireAuth } from "./billing/apps/api/src/common/middlewares/require-auth";
import { invoicesRoutes } from "./billing/apps/api/src/modules/invoices/infrastructure/http";
import { paymentsRoutes } from "./billing/apps/api/src/modules/payments/infrastructure/http";
const app = new OpenAPIHono<AppEnv>();
app.onError(globalErrorHandler);
// Your existing routes
app.get("/health", (c) => c.json({ status: "ok" }));
// Billing sub-tree
const container = buildContainer();
app.use("/billing/*", async (c, next) => {
for (const [key, value] of Object.entries(container)) {
c.set(key as keyof typeof container, value);
}
await next();
});
app.use("/billing/v1/*", requireAuth);
app
.route("/billing/v1/invoices", invoicesRoutes)
.route("/billing/v1/payments", paymentsRoutes);
serve({ fetch: app.fetch, port: 4000 });
When you only need a subset of the billing surface (e.g., entitlements + coupons for a freemium gate), import just those route modules.
// minimal-billing.ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { serve } from "@hono/node-server";
import { buildContainer, type AppEnv } from "@/container";
import { globalErrorHandler } from "@/common/middlewares/error-handler";
import { requireAuth } from "@/common/middlewares/require-auth";
import { entitlementsRoutes } from "@/modules/entitlements/infrastructure/http";
import { couponsRoutes } from "@/modules/coupons/infrastructure/http";
const app = new OpenAPIHono<AppEnv>();
app.onError(globalErrorHandler);
const container = buildContainer();
app.use("*", async (c, next) => {
for (const [key, value] of Object.entries(container)) {
c.set(key as keyof typeof container, value);
}
await next();
});
app.use("/v1/*", requireAuth);
app
.route("/v1/entitlements", entitlementsRoutes)
.route("/v1/coupons", couponsRoutes);
serve({ fetch: app.fetch, port: 3001 });
apps/api/src/index.ts - Application entry point: creates the Hono app, registers middleware, mounts all route modules, and starts the server.apps/api/src/container.ts - Dependency injection factory; instantiates all repositories, services, and use-cases and returns them as a typed container object.apps/api/src/common/middlewares/error-handler.ts - Global Hono onError handler; converts all thrown errors to a uniform JSON response.apps/api/src/common/middlewares/require-auth.ts - JWT validation middleware applied to all /v1/* routes.apps/api/src/common/middlewares/guards.ts - Route-level authorization guards (role/permission checks).apps/api/src/common/application/ports/CacheService.ts - Port interface for cache operations consumed by use-cases.apps/api/src/common/application/ports/EventBus.ts - Port interface for publishing domain events.apps/api/src/common/infrastructure/adapters/BasePostgresRepository.ts - Abstract Postgres repository with shared query helpers.apps/api/src/common/infrastructure/security/JwtService.ts - JWT signing and verification wrapper around jose.apps/api/src/common/infrastructure/security/application/AccessService.ts - Service that evaluates access control policies.apps/api/src/common/errors/DomainError.ts - Base class for all typed domain errors.apps/api/src/modules/ - One directory per domain module; each follows the same layered structure (domain, application, infrastructure/http).apps/checkout/ - Standalone checkout UI application.packages/ - Publishable client SDKs (node, next, react, browser, auth, ai, cli).scripts/ - Build and maintenance scripts shared across the workspace.turbo.json - Defines the Turborepo task graph (build, test, lint).pnpm-workspace.yaml - Declares apps/* and packages/* as workspace members.DATABASE_URL: The container will throw at startup if DATABASE_URL is not set. Always provide it in .env before running.@/ path alias not resolved: Ensure tsconfig.json has baseUrl: "./src" and paths: { "@/*": ["./*"] }. When running with tsx directly, add --tsconfig apps/api/tsconfig.json.pnpm-workspace.yaml. Running npm install at the root will break hoisting. Use pnpm install exclusively.jose requires a minimum 256-bit (32-byte) secret for HS256. A short JWT_SECRET causes silent token rejection. Use openssl rand -hex 32.AppEnv type mismatch: If you extend the container and add keys, update the AppEnv type in container.ts to include them or TypeScript will reject c.set() calls.pnpm turbo build --force to bypass the cache.I have the Revstack billing engine source code in the `source/` directory of this project.
Read `source/USAGE.md` carefully before writing any code.
The upstream package is `revstack-os`. The API is built with Hono + @hono/zod-openapi.
Key entry points:
- source/apps/api/src/index.ts (application bootstrap)
- source/apps/api/src/container.ts (dependency injection)
- source/apps/api/src/common/middlewares/ (auth, error handling)
- source/apps/api/src/modules/<module>/infrastructure/http/ (route modules)
My project is a Node.js TypeScript service using [describe your stack].
Please do the following step-by-step:
1. Add the required npm dependencies listed in USAGE.md to my package.json.
2. Copy or reference the Revstack source so it resolves the `@/` path alias correctly.
3. Create a `billing.ts` file in my project that initializes `buildContainer()`, registers
`globalErrorHandler` and `requireAuth`, and mounts these specific modules:
[list the modules you need, e.g. entitlementsRoutes, subscriptionsRoutes, customersRoutes].
4. Wire the Hono app's `fetch` handler into my existing server.
5. Add the required environment variables to my `.env.example`.
6. Show me how to test the `/v1/entitlements` route with curl once the server is running.
Do not invent any exports that are not documented in USAGE.md or visible in the source files.
The Revstack core API (apps/api/) is licensed under FSL (Functional Source License). The client SDK packages under packages/ are MIT licensed. Full license text is in source/LICENSE.md. See the upstream repository at github.com/revstackhq/revstack.
This product’s unique contribution is clean room implementation of valuable code block into AVCP compatible standard.
The full install guide and integration prompts unlock after purchase.
SaaS, AI & Subscription Products
Free