GraphQL SDL generation and query optimization for SQLModel
Project description
nexusx
Define once, serve everywhere — GraphQL for validation, REST for production, MCP for AI agents.
nexusx turns your SQLModel entities into a complete API surface without rewriting the stack at each stage. Start with a quick GraphQL POC to validate your data model, ship typed REST endpoints for your frontend, and expose the same capabilities to AI agents through MCP — all from a single set of entity definitions.
flowchart LR
idea["Idea<br/>Entities + relationships"] --> model["SQLModel<br/>Single source of truth"]
model --> poc["POC<br/>GraphQL + Voyager<br/>Validate model fast"]
model --> product["Product<br/>REST + OpenAPI<br/>Typed frontend delivery"]
model --> ai["AI-Friendly<br/>MCP tools<br/>Agent integration"]
The Problem
Frameworks force you to choose: GraphQL-first tools (Strawberry, Ariadne) give you schema generation but leave you on your own for REST endpoints. REST-first tools (FastAPI) give you OpenAPI but no GraphQL or MCP. MCP frameworks add yet another layer of duplication.
The result: three code paths that define the same entities, the same relationships, the same data shapes — in three slightly different ways. When the model changes, you fix it in three places. When a new hire joins, they learn three APIs.
nexusx gives you one model and three delivery paths. Define your entities and relationships once in SQLModel, and the framework generates all three from that single source.
How It Compares
| Tool | GraphQL auto-gen | REST + OpenAPI | MCP | N+1 prevention | Relationship auto-loading |
|---|---|---|---|---|---|
| nexusx | ✅ | ✅ | ✅ | ✅ DataLoader | ✅ implicit |
| Strawberry | ✅ | ❌ | ❌ | ⚠️ manual | ⚠️ manual loader |
| FastAPI + SQLModel | ❌ | ✅ (manual) | ❌ | ❌ | ❌ |
| Ariadne | ✅ | ❌ | ❌ | ⚠️ manual | ❌ |
| FastMCP | ❌ | ❌ | ✅ | ❌ | ❌ |
nexusx is the only framework that takes you from model definition to GraphQL, REST, and MCP without duplicating logic.
Install
pip install nexusx
pip install nexusx[fastmcp] # with MCP support
Requires Python ≥ 3.10.
Quick Start
git clone https://github.com/allmonday/nexusx.git
cd nexusx
bash start_all.sh
This launches the full demo suite:
| Service | Port | What it shows |
|---|---|---|
| GraphQL playground | 8000 | Auto-generated Schema + DataLoader relationship resolution |
| Core API (REST) | 8001 | DefineSubset DTOs with resolve_/post_/cross-layer flow |
| Auth GraphQL | 8002 | Multi-entity auth model with queries + mutations |
| Auth MCP | 8003 | Same auth model exposed as MCP tools |
| Multi-app MCP | 8004 | Two apps sharing one MCP server |
| Paginated GraphQL | 8005 | Relationship pagination (limit/offset) |
| UseCase MCP | 8006 | 4-layer progressive disclosure MCP |
| UseCase FastAPI | 8007 | Same UseCaseService served as OpenAPI-documented REST |
| Voyager | 8008 | Visual entity-relationship map |
Three Modes, One Model
nexusx offers three modes, each serving a different stage of the delivery pipeline. They share the same SQLModel entities and the same DataLoader infrastructure.
GraphQL Mode — Validate Fast
The shortest path from entities to a running API. Add @query and @mutation decorators to your SQLModel classes, and the framework generates the full GraphQL schema.
from sqlmodel import SQLModel, Field, Relationship, select
from nexusx import query, mutation, GraphQLHandler
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
posts: list["Post"] = Relationship(back_populates="author")
@query
async def get_users(cls, limit: int = 10) -> list["User"]:
"""Get all users."""
async with get_session() as session:
return (await session.exec(select(cls).limit(limit))).all()
class Post(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str
author_id: int = Field(foreign_key="user.id")
author: User | None = Relationship(back_populates="posts")
@mutation
async def create_post(cls, title: str, author_id: int) -> "Post":
"""Create a post."""
async with get_session() as session:
post = cls(title=title, author_id=author_id)
session.add(post)
await session.commit()
return post
handler = GraphQLHandler(base=SQLModel, session_factory=async_session)
Relationships resolve automatically via DataLoader — one query per relationship, regardless of result size. No selectinload, no manual joins.
When to use: early-stage validation, internal tools, rapid iteration. GraphQL's flexibility lets you test response shapes without writing DTOs.
Core API Mode — Ship Typed REST
When you're ready for production frontend delivery, DefineSubset DTOs give you typed, N+1-safe REST endpoints using the same DataLoader engine.
from nexusx import DefineSubset, ErManager
class UserDTO(DefineSubset):
__subset__ = (User, ("id", "name"))
class TaskDTO(DefineSubset):
__subset__ = (Task, ("id", "title", "owner_id"))
owner: UserDTO | None = None # auto-loaded — name matches Task.owner
class SprintDTO(DefineSubset):
__subset__ = (Sprint, ("id", "name"))
tasks: list[TaskDTO] = [] # auto-loaded — name matches Sprint.tasks
task_count: int = 0
def post_task_count(self):
return len(self.tasks) # derived after children are loaded
er = ErManager(base=SQLModel, session_factory=async_session)
Resolver = er.create_resolver()
# Per request
dtos = await Resolver().resolve(sprints)
Key concepts, in the order you'll encounter them:
| Step | What | Why |
|---|---|---|
| 1. Implicit auto-loading | Relationship fields with matching names load via DataLoader | Zero boilerplate for standard relationships |
2. resolve_* methods |
Custom loading logic via Loader(DataLoaderClass) |
Unusual joins, non-ORM data sources |
3. post_* methods |
Derived fields computed after the subtree is ready | Counts, aggregations, formatting |
| 4. Cross-layer data flow | ExposeAs (downward) + SendTo/Collector (upward) |
Ancestor context, bottom-up aggregation |
When to use: production REST endpoints, typed frontend contracts, OpenAPI documentation.
UseCase Services — One Service, Two Channels
Package your business logic as UseCaseService subclasses. The same class serves both MCP tools (for AI agents) and FastAPI routes (for web apps).
from nexusx import UseCaseService, query, UseCaseAppConfig, create_use_case_mcp_server
class SprintService(UseCaseService):
"""Sprint management."""
@query
async def list_sprints(cls) -> list[SprintSummary]:
"""Get all sprints with task counts."""
async with async_session() as session:
rows = (await session.exec(select(Sprint))).all()
return await Resolver().resolve([SprintSummary(**r.model_dump()) for r in rows])
# Expose to AI agents via MCP
mcp = create_use_case_mcp_server(
apps=[UseCaseAppConfig(name="project", services=[SprintService])],
)
mcp.run()
# Or expose as REST (in a different file)
from nexusx import create_use_case_router
router = create_use_case_router(
UseCaseAppConfig(name="project", services=[SprintService])
)
app.include_router(router)
One service class, two serving modes — no duplication.
MCP progressive disclosure: Four-layer tool hierarchy. Agents discover apps → list services → inspect method signatures → execute calls. Each layer provides just enough context to reach the next.
| Tool | Purpose |
|---|---|
list_apps() |
Discover available apps |
list_services(app_name) |
List services in an app |
describe_service(app_name, service_name) |
Method signatures + DTO type definitions |
call_use_case(app_name, service_name, method_name, params) |
Execute a method |
When to use: production-grade business logic that needs to serve both human users (REST) and AI agents (MCP).
Choosing a Mode
| If you want to... | Start with |
|---|---|
| Validate a data model quickly with flexible queries | GraphQL Mode |
| Ship typed REST endpoints for a frontend team | Core API Mode |
| Expose business capabilities to AI agents | UseCase Services |
| Do all three from one model | UseCase Services → embed DTOs inside methods |
They compose: a UseCaseService method can internally use Resolver().resolve(dtos) for Core API data assembly. The modes are not mutually exclusive — they share the same DataLoader engine and the same SQLModel entities.
AI Agent Skill
The project includes a 4-phase skill that guides AI coding agents (Claude Code, Codex) through the entire nexusx workflow:
| Phase | Focus | Output |
|---|---|---|
| 0 | Clarify the idea | Entities, relationships, use cases |
| 1 | Build the POC model | Entities + database + Voyager visualization |
| 2 | Make it queryable | @query/@mutation methods, GraphQL playground |
| 3 | Productize + AI-ready | DTOs + REST endpoints + MCP server |
Claude Code:
ln -s $(pwd)/skill ~/.claude/skills/nexusx-4phase
Then type /nexusx-4phase or describe your requirements.
OpenAI Codex (repo-scope — recommended):
mkdir -p .agents/skills && ln -s ../../skill .agents/skills/nexusx-4phase
OpenAI Codex (user-scope):
mkdir -p ~/.agents/skills && ln -s $(pwd)/skill ~/.agents/skills/nexusx-4phase
Start Codex and type $nexusx-4phase.
What the Framework Handles
| Your responsibility | Framework's responsibility |
|---|---|
| Define SQLModel entities + relationships | Auto-generate GraphQL SDL |
Write @query/@mutation methods |
Resolve relationships via DataLoader (one query per level) |
Declare DefineSubset DTOs |
Implicit auto-loading of matching relationship fields |
Write post_* methods for derived fields |
Execute them after the subtree is fully resolved |
Declare ExposeAs/SendTo/Collector |
Route cross-layer data flow automatically |
Define UseCaseService subclasses |
Discover methods, generate MCP tools + FastAPI routes |
Declare Loader(DataLoaderClass) deps |
Batch-load keys across sibling nodes (BFS-level parallelism) |
Demos
After bash start_all.sh, explore:
# GraphQL playground
open http://localhost:8000/graphql
# REST + OpenAPI docs
open http://localhost:8001/docs
# UseCase FastAPI docs — same services, REST surface
open http://localhost:8007/docs
# Entity-relationship visualization
open http://localhost:8008/voyager
Individual services:
uv run python -m demo.blog.app # GraphQL on :8000
uv run uvicorn demo.core_api.app:app --port 8001 # Core API on :8001
uv run --with fastmcp python -m demo.blog.mcp_server # GraphQL MCP (stdio)
uv run --with fastmcp python -m demo.use_case.mcp_server # UseCase MCP (stdio)
Development
./scripts/check-ci.sh # Run full CI checks (lint + type-check + tests)
uv run pytest # Run tests only
uv run ruff check src/ tests/ # Lint only
uv run mypy src/ # Type-check only
Tests use pytest-asyncio with asyncio_mode=auto. Lint uses ruff with line-length 100. Type checking uses mypy --strict.
Documentation
- API docs — per-mode guides for GraphQL, Core API, and UseCase
- Clean Architecture comparison — nexusx vs Django/DRF, Strawberry, Litestar, and more
- CLAUDE.md — development conventions and public API reference
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nexusx-2.5.2.tar.gz.
File metadata
- Download URL: nexusx-2.5.2.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e767b750b54a3ec968db8ff0f3ebbe7e106412cc46807b0de9ed4bcc8f561240
|
|
| MD5 |
8aafa6fa44c663071fc766f758b16148
|
|
| BLAKE2b-256 |
cd7570010a090727495df80013858682a9c8c50902e3be492ea128214d4440da
|
File details
Details for the file nexusx-2.5.2-py3-none-any.whl.
File metadata
- Download URL: nexusx-2.5.2-py3-none-any.whl
- Upload date:
- Size: 684.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a6df507769ba02ac949b50a54d6729fb5be189b0f6f76f20abe77a6bec947048
|
|
| MD5 |
830f015314d39cced506fb83e5735404
|
|
| BLAKE2b-256 |
dd6ddb07b0520895774a563ae1016b2893714c7a0c993ac012b616e4b36057c1
|