Skip to main content

Serve all your web apps from a single process — they don't import it, they don't know it's there

Project description

enlace

Serve all your web apps from a single process. They don't import it. They don't know it's there.

Tired of launching a separate server for each app? Register your apps with enlace — point it at where they live — and serve them all with one command. Backends (any Python ASGI app — FastAPI, Starlette, or plain functions) get mounted at route prefixes. Frontends (any static HTML/JS/CSS) get served alongside them. Your apps stay independent — no code changes, no shared dependencies.

Philosophy

Apps don't depend on enlace. enlace is an operator's tool, not a library your app imports. Your app is a standard Python module with app = FastAPI(). It runs standalone with uvicorn server:app. enlace just happens to know how to find it, mount it at a route prefix, and serve it alongside other apps. (See Zero Coupling for how enlace provides services like auth and storage without creating a dependency.)

enlace (the platform)          your app
├── fastapi                    ├── fastapi
├── uvicorn                    ├── pandas (or whatever you need)
├── pydantic                   └── ... your domain libs
└── argh
                               ← no arrow here: your app does NOT import enlace

Two principles in tension:

  1. Apps should not need to change. All aggregation logic lives in enlace. When an app is hard to mount, prefer enlace-side config (app.toml, env vars) over app code changes.

  2. Enlaced apps must still work alone. When changes are suggested, they preserve standalone operation. The pattern: env-var with current value as default — standalone uses the original, enlace overrides at build time. (See Standalone Preservation for the env-var-with-default pattern and fix classification.)

For the full rationale — including how these principles interact, where the balance sits today, and what enlace aspires to handle better — see the Design Principles document.

Using enlace with an AI agent

enlace ships with AI agent skills that let Claude Code (or any compatible agent) handle the entire workflow through natural language:

"Add my_app to the platform"
"Can s_conditions be enlaced?"
"Diagnose /path/to/app and fix what you can"
"Serve all my apps"

Install

pip install enlace

Skills are bundled with the package. To make them available to Claude Code:

# Link enlace's skills into your project (or globally)
skill link-skills "$(python -c 'import enlace; print(enlace.__path__[0])')"

# Or symlink manually
ln -s "$(python -c 'from enlace import skills_dir; print(skills_dir())')"/* .claude/skills/

Available skills

Skill What it does Trigger phrases
enlace Create apps, configure platform.toml, understand conventions, serve "add an app", "serve my apps", "configure enlace"
enlace-diagnose Analyze an app for compatibility, suggest fixes that preserve standalone operation "can this be enlaced?", "diagnose this app", "what needs to change?"
enlace-dev Modify the enlace package itself — add features, fix bugs, extend middleware "add X to enlace", "implement Y in enlace"

What the AI does for you

Onboarding an existing app: The agent runs enlace diagnose, reads the report, and presents findings in three tiers: enlace-side fixes (no app changes), app changes that preserve standalone, and warnings. It proposes specific code changes and applies them with your approval.

Creating a new app: The agent scaffolds the directory structure, writes server.py with a FastAPI app, optionally creates frontend/index.html, registers it in platform.toml, runs enlace check, and starts serving.

Day-to-day operations: The agent runs enlace serve, enlace check, enlace show-config as needed, interprets the output, and explains what's happening.

Under the hood

For those who want direct control, here's the CLI, Python API, and configuration system that the skills use internally.

Quick start

pip install enlace
# Create an app
mkdir -p apps/hello
cat > apps/hello/server.py << 'EOF'
from fastapi import FastAPI
app = FastAPI()

@app.get("/greet")
def greet(name: str = "world"):
    return {"message": f"Hello, {name}!"}
EOF

# Serve it
enlace serve
# → http://localhost:8000/api/hello/greet?name=Thor

CLI

enlace serve              # Start backend (dev mode, hot reload)
enlace show-config        # Resolved config with provenance
enlace check              # Validate config, check route conflicts
enlace list-apps          # Table: name, route, type, access
enlace diagnose <dir>     # Analyze an app for enlace compatibility

Python API

from enlace import diagnose_app, discover_apps, build_backend

# Diagnose an app
report = diagnose_app("/path/to/my_app")
print(report)              # Human-readable report
print(report.is_enlaceable)  # True if no critical blockers

# Discover and compose
config = discover_apps()
app = build_backend(config)  # FastAPI app with all sub-apps mounted

App discovery

enlace discovers apps by filesystem conventions:

apps/
├── my_tool/
│   └── server.py          # has `app = FastAPI()` → mounted at /api/my_tool
├── dashboard/
│   ├── server.py           # backend
│   └── frontend/
│       └── index.html      # served at /dashboard/
├── calculator/
│   └── server.py           # typed functions, no `app` → auto-wrapped as routes
└── blog/
    └── frontend/
        └── index.html      # frontend-only, no backend
Convention Default Override in app.toml
Route prefix /api/{dir_name} route
Entry point First of server.py, app.py, main.py entry_point
ASGI app object Attribute named app app_attr
Frontend assets frontend/index.html frontend_dir

Everything enlace infers is inspectable (enlace show-config --verbose) and overridable via app.toml, platform.toml, environment variables, or CLI flags.

Configuration

platform.toml (project root):

[platform]
apps_dirs = ["apps"]                # Directories containing app subdirs
app_dirs = ["/path/to/standalone"]  # Individual app directories
backend_port = 8000

[conventions]
entry_points = ["server.py", "app.py", "main.py"]
app_attr = "app"
frontend_dir = "frontend"

app.toml (per-app, in app directory):

route = "/api/custom-route"
entry_point = "backend/main.py"
access = "public"
display_name = "My App"

Override precedence (lowest → highest):

defaults → filesystem conventions → app.toml → platform.toml → env vars → CLI flags

App types

Type How detected How mounted
asgi_app Module has callable app attribute parent.mount(prefix, sub_app)
functions No app attr, has typed public functions Auto-wrapped as API routes
frontend_only No backend entry, has frontend/index.html Static file serving only

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

enlace-0.0.2.tar.gz (138.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

enlace-0.0.2-py3-none-any.whl (43.9 kB view details)

Uploaded Python 3

File details

Details for the file enlace-0.0.2.tar.gz.

File metadata

  • Download URL: enlace-0.0.2.tar.gz
  • Upload date:
  • Size: 138.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for enlace-0.0.2.tar.gz
Algorithm Hash digest
SHA256 951afccc5d1a870ebdd6a3149b77930cc9634acf2826a78c0d0bc47ac64b4a56
MD5 f1f6b225c85015cf5468c4bc7777b961
BLAKE2b-256 fb725fee8cb7f13f9ae4ad647e0ac9f64885c13e44ef90b71f9d4adaf6e087e4

See more details on using hashes here.

File details

Details for the file enlace-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: enlace-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 43.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for enlace-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 254d405b45e1a2691238d2463d0659ae7d33d76fb12fe016b5b495229136905d
MD5 f5ffdf5876937eeeba823e47b15b9699
BLAKE2b-256 a3df66707015a1624fd519e3008f6b1a3af3eecd1227eaf463eb7a2dab581ee4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page