https://github.com/zeljkovranjes/mono
Mono is a monolith SaaS boilerplate application built using Solid, Fastify, Turbo, TypeScript, and Ory Network
https://github.com/zeljkovranjes/mono
boilerplate monolith
Last synced: 2 months ago
JSON representation
Mono is a monolith SaaS boilerplate application built using Solid, Fastify, Turbo, TypeScript, and Ory Network
- Host: GitHub
- URL: https://github.com/zeljkovranjes/mono
- Owner: zeljkovranjes
- Created: 2025-09-20T18:28:00.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-10-02T04:22:37.000Z (9 months ago)
- Last Synced: 2025-10-02T06:15:56.458Z (9 months ago)
- Topics: boilerplate, monolith
- Language: TypeScript
- Homepage:
- Size: 698 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Mono
[](https://github.com/zeljkovranjes/mono/stargazers)
[](https://github.com/zeljkovranjes/mono/issues)
[](https://github.com/zeljkovranjes/mono)
Mono is a SaaS monolith boilerplate application with a simple multi-tenant architecture. It’s fairly barebones and missing several routes, which you can easily implement yourself. However, all the core functions needed to build those routes are already included.
NOT PRODUCTION READY. UNTIL YOU IMPLEMENT THE ENTIRE ORY FLOW AND NECESSARY ROUTES.
This project was built around utilizing Ory and in my case I utilized the Ory Network. It's recommended to use it or else you will have to heavily edit the code.
- **Authentication & Authorization** — Integrated with Ory
, providing secure user management, sessions, and role-based access control out of the box.
- **Billing & Subscriptions** — Built-in Stripe
integration for handling subscriptions, payments, invoices, and webhooks with minimal setup. You can also integrate your payment gateway by using the BillingAdapter.
- **Organizations & Projects** — Users can create organizations and, within each organization, create projects. This structure makes it easy to manage multi-tenant workflows.
- **Logging & Audit Trails** — Simple request/event logging plus audit logs for security and compliance: who did what, when, and where (scoped by org/project).
# Requirements
- **Ory Network** (Recommended to use Ory Network and not host it individually.)
- **Ory Tunnel**
- **Stripe**
- **PostgreSQL**
- **ngrok**
# CLI
You can manage the CLI via the following commands
```turbo run dev```
```turbo run build```
```turbo run test``` -- some tests will fail (didnt fix them as of yet)
```turbo run lint``` -- some lints will fail (didnt fix them as of yet)
# Installation (Local Development)
After following the instructions you can run **pnpm install** wherever is necessary. You can run the repos individually as well with **pnpm run dev** inside the desired server.
Ory Network Setup
## 1. Setting up Identity Schema
* Go into User Management -> Identity Schema -> Then scroll all the way to the bottom to **Create new schema from preset** then click **create**.
And paste the following code.
```json
{
"$id": "https://schemas.ory.sh/presets/kratos/identity.email.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "E-Mail",
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
},
"webauthn": {
"identifier": true
},
"totp": {
"account_name": true
},
"code": {
"identifier": true,
"via": "email"
},
"passkey": {
"display_name": true
}
},
"recovery": {
"via": "email"
},
"verification": {
"via": "email"
},
"organizations": {
"matcher": "email_domain"
}
},
"maxLength": 320
},
"name": {
"type": "object",
"additionalProperties": false,
"properties": {
"first": {
"type": "string",
"title": "First name",
"maxLength": 256
},
"last": {
"type": "string",
"title": "Last name",
"maxLength": 256
}
}
},
"avatar": {
"type": "string",
"title": "Avatar URL",
"maxLength": 2048
}
},
"required": [
"email"
],
"additionalProperties": false
}
}
}
```
## 2. Setting up Branding
* Go to Branding -> UI URLs and replace all of them with http://localhost:3000/{route} for example login should be http://localhost:3000/login then registration should be http://localhost:3000/signup.
## 3. Setting up Keto (Namespace & Rules)
* Go into Permissions -> Namespace & rules and paste the following code.
```
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"
// Defines a User. This class is primarily used as a type in relationships.
class User implements Namespace {}
// Defines a Organization with member and admin roles.
class Organization implements Namespace {
related: {
members: User[],
admins: User[]
}
permits = {
// A user can view the organization if they are a member or an admin.
view: (ctx: Context): boolean =>
this.related.members.includes(ctx.subject) ||
this.related.admins.includes(ctx.subject),
// Only admins can manage the organization.
manage: (ctx: Context): boolean =>
this.related.admins.includes(ctx.subject),
}
}
// Defines a Project with relationships to users and organizations using SubjectSet.
class Project implements Namespace {
related: { // <-- CORRECTED: Use a type annotation with ':'
// A project is associated with one organization.
organization: Organization[],
// Owners can be individual users or the entire set of admins from the related organization.
owners: (User | SubjectSet)[],
// Editors can be individual users or the entire set of admins from the related organization.
editors: (User | SubjectSet)[],
// Viewers can be individual users, or the entire set of members or admins from the related organization.
viewers: (User | SubjectSet | SubjectSet)[]
}
permits = {
// A user can view if they are a viewer, editor, or owner.
// The .includes() check automatically resolves if the user is part of a related SubjectSet.
view: (ctx: Context): boolean =>
this.related.viewers.includes(ctx.subject) ||
this.related.editors.includes(ctx.subject) ||
this.related.owners.includes(ctx.subject),
// A user can edit if they are an editor or an owner.
edit: (ctx: Context): boolean =>
this.related.editors.includes(ctx.subject) ||
this.related.owners.includes(ctx.subject),
// Only owners can manage the project.
manage: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject),
}
}
```
## 4. Setting up OAuth2
* Go into Authentication -> Social Sign-In (OIDC) -> Thenc lick Add new OpenID Connect Provider.
The actual dashboard that is located in apps/dashboard should automatically resolve the OpenID Connect providers. I know it works for Google, I believe GitHub and Microsoft as well.
## 5. Setting up Stripe Customer Creation via Ory Actions
* The whole point of this is to create a stripe customer on registration. Simply go to Authentication -> Actions & Webhooks -> **Create new Action**
**I'm going to assume you know how to setup ngrok and it's best to use a static address for this** simply point ngrok to the *billing-api* server.
And use the following settings:
Flow: **Registration**
Execution: **After**
Method: **OpenID Connect (OIDC)**
URL: {ngrok_url_to_billing_api}
Method: **POST**
Action HTTP body
```javascript
function(ctx) {
id: ctx.identity.id,
email: ctx.identity.traits.email,
name: if ctx.identity.traits.name != null then
(ctx.identity.traits.name.first + " " + ctx.identity.traits.name.last)
else "",
avatar: if ctx.identity.traits.avatar != null then ctx.identity.traits.avatar else "",
}
```
Asynchronous: **OFF**
Process response: **OFF**
When it shows authentication Click Authentication type: **Key** and put the Transport mode to **Header**.
key name: **Authorization**
Key value: Bearer {API_SECRET_KEY} (the value from the .env) then click **save action**. MAKE SURE YOU INCLUDE "Bearer"!!!
## 6. Setting up Ory Tunnel
* Install the Ory Tunnel CLI here https://www.ory.sh/docs/cli/ory-tunnel. Then run the following command.
```
ory tunnel --project {project_id} --cookie-domain localhost http://localhost:3000
```
Repository Setup
The repository structure is not really optimized.
## 1. Environment Setup
I'm going to assume you will be using the Ory Network. So at the mono repo root.
paste the following code
```
NODE_ENV=development # not needed in vite because it automatically gets injected
LOG_LEVEL=trace # trace | debug | info | warn | error | fatal
ORY_ADMIN_API_TOKEN=ory_admin_api_token
REMEMBER_CONSENT_SESSION_FOR_SECONDS=3600
COOKIE_SECRET=changeme12345adssd12dads12
CSRF_COOKIE_NAME=__Host-safeoutput.com-x-csrf-token
CSRF_COOKIE_SECRET=somesecretvaluedsadas
DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES=true
MOCK_TLS_TERMINATION=false
API_SECRET_KEY=an_api_super_secret_key
STRIPE_SECRET_KEY=stripe_secret_key
STRIPE_WEBHOOK_SECRET=stripe_webhook_secret
POSTGRES_DATABASE_URL=postgres://{username}:{password}@{host}:{port}/{database}
OTEL_EXPORTER_URL=http://localhost:4318/v1/traces
VITE_ROOT_DOMAIN=http://localhost:3000 # e.g. http://localhost:3000
VITE_ORY_SDK_URL=http://localhost:4000 # e.g. http://ory-network:4000
```
**Then copy and paste the mono repo env into apps/dashboard.**
## Middleware
I created a few helpful middlewares that might come use to you when you're building with fastify. If you would like to know how to use them in fastify please refer to the apps/core-api/routes
requireAuthMiddleware
requireNoAuthMiddleware
setSession (Deprecated) please dont use this.
requirePermissionMiddleware
requireSecretMiddleware