Open Source GraphQL Conf 2026

One Graph.
Any City.
Every Data Source.

A Viaduct-powered GraphQL graph over a whole city.
One endpoint for maps, dashboards, and AI.

Viaduct Kotlin/JVM PostgreSQL + PostGIS MapLibre
01 / the problem

Cities expose data through separate systems.

Try answering: what's happening on this block?

You end up pulling permits, zoning, transit, and 311 from different places.

GET /parcels/:id different shape · different auth · different pagination
GET /zoning?parcel=... different shape · different semantics
GET /permits?parcel=... different shape · different consistency
GET /transit?near=... different shape · different rate limits
GET /311?parcel=... different shape · different error model

You stitch it together in the client.
That logic gets copied into every consumer.

Five systems. One query.

Stop stitching endpoints.

Ask for the shape you need.
Resolve it in one place.

REST 5 calls · 5 shapes
GET /parcels/:id
GET /zoning?parcel=...
GET /permits?parcel=...
GET /transit?near=...
GET /311?parcel=...

// client joins everything
GraphQL 1 call · 1 shape
query BlockContext($id: ID!) {
  parcel(id: $id) {
    address
    zoning { allowedUses }
    permits { status issuedAt }
    nearbyTransit { stops { name } }
    complaints { count openCount }
  }
}

GraphQL moves the join into the server.

Same data. Same database.
What changes is where the composition happens.

// where it breaks

The second year of a
shared graph.

Year one is easy. The schema is small.
By year two the system grows. More types. More teams.

The questions change.

  • A permit references a parcel. Who owns that join?
  • A field needs to change. Who signs off?
  • A query slows down in production. Who gets paged?

These are coordination problems.
The schema just makes them visible.

One team usually takes it on.
The graph turns into a bottleneck.

Same query. Different output.

The X-UrbanMesh-Profile header controls what resolves.

The query stays the same.
The response changes.

No new endpoints. No versioning.

query { parcel { insights } }
Planner zoningCompliance
Resident walkabilityScore
Developer disruptionRisk
Urban Planner

Zoning compliance, permit history, land-use trends

zoningCompliancepermitHistorylandUseTrends
Profile: urban-planner
Resident

Nearby trees, transit options, walkability, open 311 cases

walkabilityScorenearbyTreesopen311Cases
Profile: resident
Developer

Permit timelines, home values, disruption risk

permitDurationDaysmedianHomeValuedisruptionRisk
Profile: real-estate-developer
+5 more profiles Transit PlannerSmall Business OwnerEnvironmental AdvocateData JournalistAccessibility Advocate

Five domains. One schema.

Each domain defines its part of the system.

entities · relationships · boundaries

[:parcels] Land
  • Parcels
  • Zoning
  • Land Use
400K+ rows
[:transit] Transit
  • Stops
  • Routes
  • Schedules
1.5M+ rows
[:civic] Civic
  • 311 Cases
  • Permits
1.6M+ rows
[:environment] Environment
  • Trees
  • Green Spaces
190K+ rows
[:census] Census
  • Demographics
  • Boundaries
400+ rows

Relationships are defined once.
Clients do not rebuild them.

[composed at runtime]
LandTransitCivicEnvironmentCensus
One Graph Viaduct

Modular inside. One graph outside.

GraphQL handles querying.
Viaduct handles composition.

Ownership lives in the modules. Not in a shared layer.

Each domain defines its data and relationships. Viaduct assembles the schema at runtime.

No central team stitching everything together.

Any client browser, mobile, notebook, agent
POST /graphql X-UrbanMesh-Profile: <profile>
Viaduct Server Kotlin/JVM · modular schema composition
:parcels :permits :transit :civic :census :environment :geography
[each module owned independently] · [one unified schema]
PostgreSQL + PostGIS

The difficult part was ownership.

Querying was straightforward. Defining ownership was not.

Does a permit belong to :parcels or :permits?
Where is that edge defined?
Who is responsible when it breaks?

constraint

This demo uses offline data.

Production adds failure modes:
stale caches · partial failures · rate limits.

tradeoff

Clear ownership improves the system.

It also requires discipline.

A shared schema still creates shared responsibility.

The graph already works as an interface.

The AI client uses the same GraphQL API as the UI.

No extra layer. No custom endpoints.

UI client · AI client Claude via MCP
↓ same GraphQL
One schema types · fields · relationships

Agents use the same structure as humans.

// demo

These are compositions
of the same graph.

  1. 01 16th & Mission

    One block, five systems

    Resolve permits, zoning, transit, 311, and census in one query.

    “Five data sources. One query. No client joins.”

  2. 02 16th & Mission

    Same query, different output

    Planner sees zoning compliance. Resident sees walkability. Developer sees disruption risk.

  3. 03 Outer Sunset

    The invisible city

    Canopy coverage, green space access, commute patterns.

This shows what composition looks like in practice.

One query resolves across systems
One schema serves different users
One graph supports both UI and AI

A city behaves like a distributed system.
This is one way to structure it.