Most web applications are built on relational databases (PostgreSQL, MySQL) or document stores (MongoDB, DynamoDB). The data model is defined in application code — ORM schemas, GraphQL type definitions, or JSON schemas — and the database is treated as a dumb persistence layer. The API, the validation rules, and the UI forms are all separate concerns, maintained separately, and often drift apart.

What if the data model itself could drive all of those?

This article explores using RDF (Resource Description Framework, pronounced as individual letters: "R-D-F"), SPARQL (SPARQL Protocol and RDF Query Language, pronounced "sparkle"), and SHACL (Shapes Constraint Language, pronounced "shackle") as the foundation for a web application. The examples come from an experimental application I've been working on — a knowledge graph of an enterprise product ecosystem that powers both an AI-facing MCP server and a human-facing web UI, both driven entirely by the same data model.

The goal here is not to argue that every web application should be built this way. Rather, it is to demonstrate that semantic web technologies — standards that have been maintained by the W3C for over two decades, and are still actively updated (see v1.2 of RDF, SHACL, and SPARQL) — offer a viable and innovative foundation for an AI-first software development lifecycle. In an era where AI models are becoming primary consumers of APIs and data, an approach built on open, well-specified standards adds durable value: the ontology that drives your web UI today can drive your MCP tools tomorrow, and both benefit from the same validation, internationalization, and schema evolution — without framework churn or vendor lock-in.1

Key Terms

Before diving in, a brief glossary of the core technologies:

  • RDF — A W3C standard for representing data as a graph of triples: subject → predicate → object. Instead of rows in a table, you have statements like Product → hasCategory → CloudComputing. Think of it as a universal data format where everything is a relationship.

  • SPARQL — The query language for RDF data, analogous to SQL for relational databases. Where SQL queries tables with rows and columns, SPARQL queries graphs by matching patterns of triples.

  • OWL — Web Ontology Language (pronounced "owl"). A vocabulary built on RDF for defining classes, properties, and relationships — essentially the schema definition language for your knowledge graph. Analogous to CREATE TABLE in SQL or type definitions in GraphQL.

  • SHACL — A language for expressing validation rules over RDF data. Where JSON Schema validates JSON documents, SHACL validates RDF graphs. The key difference: SHACL rules are themselves RDF data, meaning they can be queried, modified, and reasoned about programmatically.

  • Triplestore — A database optimized for storing and querying RDF triples. The equivalent of a relational database for graph data.

The Core Idea: One Schema Rules Everything

In a typical application stack, you define your data model multiple times:

  1. Database schemaSQL tables or document shapes
  2. API schema — REST endpoints, GraphQL types, or protobuf definitions
  3. Validation rulesZod schemas, Joi validators, or custom code
  4. UI formsHTML form fields, often hand-coded per entity type
  5. DocumentationOpenAPI specs, README tables

In the RDF/SPARQL/SHACL approach, you define the model once as an OWL ontology:

# Define a "Product" class (like CREATE TABLE products)
rho:Product a owl:Class ;
  # Human-readable labels in multiple languages
  rdfs:label "Product"@en, "Producto"@es, "製品"@ja ;
  # Description for documentation and AI context
  rdfs:comment "A product or service offering"@en ;
  # URL slug — this class will be browsable at /en/products
  rho:slug "products" .

# Define a "category" property (like ALTER TABLE products ADD COLUMN category)
rho:category a owl:ObjectProperty ;
  # This property belongs to Product (like a foreign key constraint)
  rdfs:domain rho:Product ;
  # It points to a ProductCategory instance (the referenced table)
  rdfs:range rho:ProductCategory ;
  rdfs:label "Category" .

This single definition drives:

  • URL routingrho:slug "products" maps to /{lang}/products
  • API endpoints — a find-products tool is auto-generated at startup
  • Form fields — the edit form for a Product shows a Category dropdown
  • ValidationSHACL shapes enforce that Category is required and must reference a valid ProductCategory
  • i18n — the label renders as "Product" in English, "Producto" in Spanish, "製品" in Japanese
  • Graph visualization — the class appears as a node in the ontology graph with edges to related classes

How the MCP Server and Web Application Work Together

One of the more distinctive aspects of this architecture is that the AI-facing MCP2 server and the human-facing web application are not separate systems — they share the same triplestore, the same ontology, and the same validation rules. They are two interfaces to the same knowledge graph.

At startup, the MCP server introspects the OWL ontology to discover what classes exist, what properties they have, and how many instances of each class are stored. When a class crosses a configurable threshold (e.g., 10 instances), the server dynamically generates a finder toolfind-products, find-partners, find-certifications — that AI clients can call without writing SPARQL. These tools accept natural parameters ({ category: "Cloud Computing" }) and translate them into the appropriate SPARQL queries behind the scenes.

This means:

  • Adding a new entity type to the ontology automatically creates a new MCP tool — no code changes, no deployment
  • The same SHACL validation that protects the web UI forms also validates MCP tool inputs — an AI client cannot create an invalid Product any more than a human user can
  • Changes made via the MCP server appear in the web UI in real time via CDC (Change Data Capture) notifications, and vice versa
  • The tool descriptions sent to AI clients embed the full data model summary — classes, properties, instance counts — so the AI always has up-to-date schema context

The collaborative effect is powerful: an AI assistant can create and categorize hundreds of product entries via MCP tools, and a human editor can immediately review and refine them in the web UI — both working against the same live data, governed by the same rules.

The Positives

1. Schema-Driven Everything

The most compelling advantage is elimination of schema duplication. In the experimental application, adding a new entity type to the knowledge graph requires:

  1. Define the OWL class with rdfs:label, rdfs:comment, and rho:slug
  2. Define its properties with rdfs:domain and rdfs:range
  3. Optionally add SHACL constraints

That's it. The system automatically:

  • Creates a new page at /{lang}/{slug} with list, detail, and create/edit views
  • Generates a find-{slug} MCP tool for AI clients (once the class has 10+ instances)
  • Derives form fields from SHACL shapes or OWL property definitions
  • Includes the class in the graph visualization
  • Adds it to the navigation

In the experimental application, when a user creates a new class via the create-class MCP tool, the UI picks it up on the next CDC event — no code changes, no deployment, no migration.

2. SHACL as Declarative Validation

SHACL shapes are validation rules written as RDF — they live alongside the data, queryable and introspectable:

# "A Product must have exactly one full name (a text string)
#  and at least one category (a reference to another entity)"
rho:ProductShape a sh:NodeShape ;
  # This shape applies to all instances of the Product class
  sh:targetClass rho:Product ;
  sh:property [
    # The "fullName" field...
    sh:path rho:fullName ;
    sh:minCount 1 ;       # ...is required (at least 1)
    sh:maxCount 1 ;       # ...and single-valued (at most 1)
    sh:datatype xsd:string ; # ...and must be a text string
    sh:name "Full Name"   # ...labeled "Full Name" in forms
  ] ;
  sh:property [
    # The "category" field...
    sh:path rho:category ;
    sh:minCount 1 ;       # ...is required
    sh:nodeKind sh:IRI ;  # ...and must be a reference to another entity (not plain text)
    sh:name "Category"    # ...labeled "Category" in forms
  ] .

The UI queries these shapes to generate forms:

  • sh:minCount 1HTML required attribute
  • sh:datatype xsd:date<input type="date">
  • sh:nodeKind sh:IRI → dropdown or multi-select with entity search
  • sh:patternHTML pattern attribute for client-side regex validation
  • sh:maxCount absent → multi-valued field (searchable multi-select)

The same shapes validate server-side when Oxigraph3 receives a SPARQL UPDATE. If validation fails, the triplestore returns HTTP 422 with structured violations that the UI maps back to individual form fields. Client and server validate against the same rules with zero duplication.

3. Graph Queries Are Natural for Connected Data

SPARQL excels at traversing relationships. Finding all content related to a product:

SELECT ?content ?type ?label WHERE {
  { ?content rho:forProduct rho:product-ocp ; a rho:TrainingCourse . BIND("Training" AS ?type) }
  UNION
  { ?content rho:forProduct rho:product-ocp ; a rho:BlogPost . BIND("Blog" AS ?type) }
  UNION
  { ?content rho:forProduct rho:product-ocp ; a rho:SummitSession . BIND("Session" AS ?type) }
  ?content rdfs:label ?label .
}

Try writing that join in SQL across normalized tables with foreign keys. In SPARQL, the relationship traversal is the natural way to query.

The experimental application uses this for the graph visualization on every page — the Cytoscape.js4 graph is populated by a SPARQL query that discovers edges between classes by scanning actual instance data:

SELECT DISTINCT ?sourceClass ?prop ?targetClass WHERE {
  ?s a ?sourceClass .
  ?s ?prop ?o .
  ?prop a owl:ObjectProperty .
  ?o a ?targetClass .
}

This means the visualization adapts to the data — add a new relationship type and the graph shows it without code changes.

4. Built-in Multilingual Support

RDF has first-class language tags on string literals:

rho:Product rdfs:label "Product"@en, "Producto"@es, "製品"@ja, "منتج"@ar .

SPARQL can filter by language:

OPTIONAL { ?entity rdfs:label ?label . FILTER(lang(?label) IN ("es", "en", "")) }

In the experimental application, this powers 8-language support across the entire UI — and critically, the same translation mechanism is used for both content and page elements. The data labels ("Product", "Category") and the UI chrome (buttons, headers, navigation labels) all use language-tagged RDF literals queried at render time. Translations for UI elements live in a SPARQL named graph (<urn:rho:i18n>) alongside the content translations.

This unified approach means that adding a language is an incremental, data-only operation — you add tagged literals for the strings you want to translate, and untranslated strings fall back to English. There are no JSON translation files to maintain, no i18n framework to configure, and no code changes required. A translator can add Spanish labels to 20 out of 200 terms, and the application gracefully renders those 20 in Spanish while falling back to English for the rest — no "all or nothing" translation bundles.

5. The Ontology Is the API Contract

Because the MCP server introspects the ontology at startup, the API adapts to the data model. When a new class crosses the 10-instance threshold, a new finder tool appears. When a property is added, it becomes a query parameter. This is fundamentally different from REST or GraphQL where the API must be explicitly designed, implemented, and versioned.

The tool description for sparql-query embeds the full data model summary — classes, properties, instance counts — so AI clients always have the schema context before querying. The schema is the documentation.

The Negatives

1. Query Performance Considerations

SPARQL queries with multiple OPTIONAL clauses and FILTER expressions perform well on embedded stores (RocksDB5: 3–6ms per query). On distributed backends like TiKV6, the same queries can also achieve single-digit millisecond latency — but only with proper tuning.

With a correctly sized block cache (large enough to hold the working set in memory), bloom filters on the default column family, and pod affinity to co-locate the query engine with TiKV nodes, the experimental application achieves 6–100ms for most queries on a 50K-triple dataset across a 3-node TiKV cluster. Finder queries (substring search across 600+ entities) complete in ~10ms, paginated list queries in ~30ms, and cross-entity relationship traversals in ~40–80ms. The most complex query — a nested sub-SELECT that discovers property usage patterns by scanning all instance data — takes ~7 seconds, a cost driven by query structure rather than storage latency.

These numbers represent a 50–300x improvement over the initial deployment, where the same queries took 1–20 seconds. The dominant factor was TiKV's RocksDB block cache: at the default 256MB, every scan required disk I/O across the network; at 1.5GB, the entire dataset fits in memory, reducing each TiKV RPC to a memory-only operation. Sustained throughput improved from 4 req/s to over 2,000 req/s.

The OPTIONAL pattern used for language fallback (OPTIONAL { ?x rdfs:label ?l . FILTER(lang(?l) IN ("es", "en", "")) }) adds some overhead — each OPTIONAL generates a separate scan, whereas in SQL a WHERE lang IN ('es', 'en') would be a single index lookup. The experimental application mitigates this with aggressive caching (pre-rendered pages, 5-minute TTL on introspection) and CDC-driven cache invalidation.

2. Schema Evolution Without Traditional Migrations

RDF is schemaless by nature. There is no ALTER TABLE — you simply start adding triples with new predicates. This is both a feature (zero-downtime schema evolution, no migration scripts) and a challenge (no built-in migration history or rollback mechanism).

SHACL shapes help — they enforce constraints going forward — but they don't retrofit existing data. If you add sh:minCount 1 to a property, existing entities without that property become invalid and need to be updated.

The experimental application mitigates this through oxigraph-cloud7, which extends Oxigraph with a transaction changelog — every SPARQL UPDATE is recorded with the full set of inserted and removed quads. This provides an audit trail analogous to migration history, and the CDC engine (built on the W3C Solid Notifications Protocol8) broadcasts changes in real time, enabling downstream systems to react to schema evolution as it happens.

3. Tooling Ecosystem Is Smaller

The SQL/ORM ecosystem has decades of tooling: migration frameworks, ORMs, admin panels, monitoring dashboards, backup tools, IDE integrations. The RDF ecosystem has excellent standards (W3C-specified, well-documented) but fewer production-ready tools.

The experimental application uses Oxigraph (a Rust SPARQL engine) extended with SHACL validation, a changelog, and a CDC engine based on the W3C Solid Notifications Protocol. These features don't exist in vanilla Oxigraph — they were built as extensions in oxigraph-cloud. In PostgreSQL, you'd get triggers, audit logging, and logical replication out of the box.

4. Learning Curve

SPARQL is a powerful language but unfamiliar to most web developers. Concepts like triple patterns, graph patterns, OPTIONAL, UNION, FILTER, and named graphs require a mental model shift from rows-and-columns thinking. The closest analogy: if you're comfortable with SQL JOINs and subqueries, SPARQL graph patterns will feel conceptually similar — but the syntax and data model are different enough to require investment.

The experimental application's MCP server mitigates this by auto-generating finder tools that abstract away SPARQL — users call find-products with { category: "Cloud Computing" } instead of writing queries. But developers maintaining the system need SPARQL fluency.

5. Blank Nodes Add Complexity

In RDF, every piece of data is normally identified by a URI — for example, rho:Product or rho:category. A blank node is an anonymous node with no URI: it represents "something exists" without giving it a permanent name. Think of it like an anonymous object in JSON{ "minCount": 1, "datatype": "string" } — embedded inside a larger structure but not independently addressable.

SHACL shapes use blank nodes extensively for property constraints (the [ sh:path ... ; sh:minCount 1 ] blocks in the examples above are blank nodes). This creates practical problems:

  • No stable identity — blank nodes get new internal IDs every time the data is loaded, so you cannot bookmark or link to a specific constraint
  • Hard to query — you cannot write SELECT ... WHERE { <_:constraint123> sh:minCount ?min } because the ID changes between loads
  • SPARQL limitationsINSERT DATA statements cannot reference blank nodes, complicating programmatic shape creation

The experimental application works around this by loading shapes into the default graph as regular RDF and deduplicating results by property path. It's manageable but adds friction that named nodes would avoid.

Similarities to Other Approaches

vs. GraphQL

Both SPARQL and GraphQL are graph query languages. Key differences:

  • Schema definition: GraphQL schemas are written in SDL and live in application code. RDF ontologies are data — stored in the triplestore, queryable, and modifiable at runtime.
  • Type system: GraphQL has a strict type system enforced at query time. RDF is open-world — anything can have any property. SHACL adds closed-world validation on writes but doesn't restrict reads.
  • Traversal: SPARQL can traverse arbitrary-depth relationships in a single query (property paths). GraphQL requires explicit resolver chains.
  • Ecosystem: GraphQL has massive tooling (Apollo, Relay, Hasura, Prisma). SPARQL tooling is specialized and smaller.

vs. REST + JSON Schema

REST APIs define endpoints manually. The experimental application's approach is closer to hypermedia — the ontology describes what types exist and how they relate, and the UI/API adapts automatically. JSON Schema is used for validation in REST; SHACL serves the same role but is itself queryable RDF data.

vs. Event-Driven / CQRS

The experimental application's CDC approach (WebSocket and SSE channels delivering W3C Solid Notifications from the triplestore) resembles CQRS with event sourcing. The changelog records transactions, capturing inserted and removed quads — similar to an event log. The UI subscribes to changes via SSE or WebSocket and re-renders — similar to a read model reacting to events. The difference is that the "event" is an ActivityStreams 2.0 JSON-LD notification containing RDF quad deltas, not a domain event.

vs. Headless CMS

The closest analogy might be a headless CMS where the content model drives the editing interface. In Contentful or Strapi, you define content types and the admin UI generates forms. In the experimental application, you define OWL classes and the forms generate themselves. The RDF approach is more flexible (arbitrary relationships, multilingual natively, graph queries) but less polished (no drag-and-drop form builder, no WYSIWYG).

When Would You Use This?

This approach shines when:

  • The data is inherently a graph — products linked to partners linked to certifications linked to people
  • The schema evolves frequently — new entity types added without migrations or deployments
  • Multiple consumers need different views — AI clients, web UI, API consumers all read the same data through different lenses
  • Multilingual support is fundamental — not bolted on via i18n files but embedded in the data
  • Validation rules should be data — queryable, modifiable at runtime, shared between client and server
  • AI integration is a first-class concern — the same ontology drives both human and AI interfaces

It's less suitable when:

  • Performance is critical and access patterns are predictable — use PostgreSQL with proper indexes
  • The team isn't familiar with RDF — the learning curve is real
  • You need a rich ecosystem of toolsORMs, admin panels, migration frameworks
  • The data is naturally tabular — financial records, time series, logs

Conclusion

Using RDF/SPARQL/SHACL as a web application foundation is unconventional but surprisingly effective for the right use case. The key insight is that the ontology becomes the single source of truth — it defines the data model, the API surface, the validation rules, the UI structure, and the internationalization, all in one place.

The tradeoffs are real: query performance considerations on distributed backends, a smaller tooling ecosystem, and a steeper learning curve. But the elimination of schema duplication — one definition that drives forms, APIs, validation, visualization, and i18n — is a powerful simplification that traditional stacks achieve only with significant framework investment.

More importantly, this approach is built for the AI era on the foundation of established standards. The W3C specifications underpinning RDF, SPARQL, OWL, and SHACL have been stable for decades and will outlast any framework cycle. The experimental application demonstrates that a production web application can be built this way: 8 languages, SHACL-driven forms, auto-generated APIs, real-time CDC, graph visualization, and dynamic MCP tooling — all from a 500-line ontology and a SPARQL endpoint.


1

The core specifications — RDF 1.1 (2014), SPARQL 1.1 (2013), OWL 2 (2012), SHACL (2017) — are W3C Recommendations with stable implementations across multiple languages and platforms.

2

Model Context Protocol (MCP) is an open standard, originally developed by Anthropic, that provides a standardized way for AI models to discover and call external tools, access data sources, and interact with services.

3

Oxigraph is a high-performance, embeddable RDF triplestore and SPARQL engine written in Rust. It supports SPARQL 1.1 queries and updates, and can use RocksDB or TiKV as storage backends.

4

Cytoscape.js is an open-source JavaScript library for graph visualization and analysis, used in the experimental application to render interactive ontology graphs in the browser.

5

RocksDB is an embeddable, high-performance key-value store developed by Meta (Facebook), used by Oxigraph as its default local storage backend.

6

TiKV is a distributed, transactional key-value store originally created by PingCAP and now a CNCF graduated project. It provides the distributed storage backend option for Oxigraph.

7

oxigraph-cloud is the Chapeaux project's distributed Oxigraph deployment, extended with SHACL validation, a transaction changelog, and a CDC engine built on the W3C Solid Notifications Protocol.

8

The W3C Solid Notifications Protocol defines a standard mechanism for subscribing to and receiving notifications about changes to resources — used in the experimental application for real-time CDC event delivery.

← All posts