Hybrid Cloud

Microservices Framework

Domain-driven Frappe services

The Frappe Microservice Framework lets you decompose the monolith gradually. Each service gets its own database and bounded context, authenticates against the central site, and keeps Frappe-style controllers and document hooks — without a fork of Frappe core.

Language
Python
License
Open source
Python
3.11 · 3.14
Frappe
v15 stable · v16 beta
Requires
Central Site + SaaS Platform
QUICKSTART
Install
# Prerequisite: a running Central Site (the Frappe site that# authenticates requests) with the saas_platform app installed —# that is what supplies the tenant_id this framework filters on. pip install frappe-microservice # Or pin it straight from Gitpip install git+https://github.com/vyogotech/erpnext-microservices-lib.git
Configure
# The framework is configured entirely through the environment,# so the same image runs in every deployment. export FRAPPE_SITE="dev.localhost"export FRAPPE_SITES_PATH="/home/frappe/frappe-bench/sites"export CENTRAL_SITE_URL="http://central-site:8000"   # auth providerexport DB_HOST="orders-db"                           # this service's own DBexport LOG_LEVEL="INFO"export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4317"
First service
from frappe_microservice import create_microservice app = create_microservice("orders-service") # Authenticated user is injected for you@app.secure_route("/hello", methods=["GET"])def hello(user):    return {"message": f"Hello {user}!"} app.run()
Zero-code CRUD
from frappe_microservice import create_microservice app = create_microservice("orders-service") # Generates the full REST surface for a DocTypeapp.register_resource("Sales Order") # GET    /api/resource/sales-order# POST   /api/resource/sales-order# GET    /api/resource/sales-order/{name}# PUT    /api/resource/sales-order/{name}# DELETE /api/resource/sales-order/{name} app.run()

What Microservices Framework gives you

Secure by default

Every endpoint requires authentication against the central site. The authenticated user is injected into your handler, so there is no unauthenticated code path to forget about.

Independent database per service

Each service owns its own database — the bounded context principle — so a service can be deployed, scaled and migrated without coordinating with the monolith.

Tenant-aware database wrapper

Automatic tenant_id filtering on reads and writes makes cross-tenant leakage a framework-level guarantee rather than a code review checklist item.

Zero-boilerplate CRUD

register_resource('Sales Order') generates the full REST surface — list, create, read, update, delete — against a real Frappe DocType.

Frappe-style document hooks

validate, before_insert, after_insert and friends work the way you already expect, without patching Frappe core or the monolith's database.

Frappe-native

DocTypes, the ORM and the API conventions carry over, so the code your team already knows how to write keeps working in a distributed topology.

Under the hood

Auth context injection, automatic tenant filtering and Frappe-style controllers in a distributed architecture — so a service extracted from the monolith still looks like Frappe code to the team maintaining it. Configuration is environment-only, and a Containerfile ships with the library for production builds.

Read the source

Frequently asked questions

What do I need before installing it?

A running Central Site — the Frappe site that authenticates requests — with the SaaS Platform app installed on it. SaaS Platform is what adds the tenant_id field the framework filters on, so without it there is no tenant context to inject. Each service then gets its own separate database.

How is a service configured?

Entirely through environment variables: FRAPPE_SITE, FRAPPE_SITES_PATH, CENTRAL_SITE_URL, DB_HOST, LOG_LEVEL and OTEL_EXPORTER_OTLP_ENDPOINT. That keeps one image runnable across every environment, and a Containerfile ships with the library for production builds.

Do I have to rewrite the monolith to use this?

No. It is designed for gradual decomposition: extract one bounded context at a time into its own service and database, while the monolith keeps serving everything else. The central site stays the identity provider throughout.

Which Frappe versions are supported?

The main and version-15 branches target Frappe/ERPNext v15 on Python 3.11 and are stable. A version-16 branch targets v16 on Python 3.14 and is in beta, with a develop branch tracking Frappe develop experimentally.

How is cross-tenant data access prevented?

A tenant-aware database wrapper applies tenant_id filtering to queries automatically, using the tenant context resolved during authentication. Handlers cannot accidentally query outside their tenant because the filter is applied below the handler.

Running Microservices Framework in production?

We build and maintain these tools, and we help teams adopt them. Tell us what you are trying to ship and we will tell you honestly whether this is the right piece.

Talk to the team