Back to Portfolio

2026 ยท Senior Software Engineer

Eden Cooperative

At Eden Cooperative, I architected and built the core data engine for the community directory using a graph database, enabling AI-powered search and enhancing connections among users, businesses, and offers while ensuring privacy and scalability.

TypescriptAI/MLPostgreSQL

Eden Cooperative โ€” Community Graph API

๐ŸŒ > Role: Senior Software Engineer ยท Year: 2026 ยท Stack: NestJS, TypeScript, Neo4j, Valkey, Anthropic Claude, OpenAI Embeddings, Google Maps, Cloudflare Images/Stream

Overview

At Eden Cooperative, I architected and built the core data engine behind Eden's community directory โ€” the system of record for every person, business, offer, ask, and connection in the network, made instantly searchable with AI. This is the foundation layer that every future phase of Eden's vision is designed to extend, not replace.

The v1 shipped a working community directory โ€” profiles, listings, AI-powered search, privacy-controlled contacts, media and links, anonymous browsing โ€” but the real engineering work was in the architecture underneath it.

Why a graph database

Eden's data is inherently a network. People know people. People own businesses. Businesses make offers. Offers are tagged with skills and industries. People live in communities. Communities overlap. The value of Eden is in these connections .

SQL and document stores treat connections as an afterthought โ€” joins get exponentially more expensive the further you traverse. A graph database stores relationships as first-class data, which unlocks:

  • Multi-hop AI search โ€” a query like "I need a plumber near me who does emergency work" traverses chunks โ†’ tags โ†’ businesses โ†’ locations in a single pass instead of dozens of joins.
  • Recommendations that compound. Every new node makes every existing node more findable. The graph is the recommendation engine.
  • Context-dependent privacy. A business phone number can be public on a business profile but restricted on an offer listing โ€” modeled as a property on the CAN_CONTACT relationship rather than the contact itself.
  • Locality-bound performance. Query cost scales with the size of the traversed neighborhood, not total graph size. A community of 50,000 performs the same as one of 500.

What I built

NestJS Graph API over Neo4j

A stateless TypeScript service (NestJS 11, Express, neo4j-driver 5) that owns all reads and writes to a single Neo4j instance. Every authorization check, every query, every pipeline trigger is expressed as raw Cypher โ€” no ORM, no query builder โ€” with multi-step writes wrapped in explicit session.executeWrite transactions.

Graph schema

Modeled the full domain as nodes and relationships:

  • Entities: Person , Business , Offer , Ask , Tag , Location , Chunk , Media , Link , ContactChannel , AnonymousVisitor
  • Ownership & access: OWNS , HAS_ACCESS , HAS_OFFER , HAS_ASK
  • Content: HAS_CHUNK , TAGGED , HAS_LOCATION , HAS_MEDIA , HAS_LINK
  • Contacts: OWNS_CONTACT for ownership, CAN_CONTACT {privacy} for per-profile attachment โ€” the same contact node can be public on one profile and registered on another
  • Behavioral: HAS_VIEWED , HAS_CONTACTED , SIGNED_IN_AS Backed by uniqueness constraints, vector indexes on chunk and tag embeddings (1536-dim cosine), a point index for geospatial proximity, and Lucene fulltext indexes on chunk content and tag names for BM25 keyword retrieval.

GraphRAG query pipeline

Designed a hybrid retrieval pipeline that runs in ~200โ€“500ms end-to-end:

  1. Embed the user query โ€” Valkey cache hit-or-miss against OpenAI text-embedding-3-small .
  2. Four parallel retrievals against Neo4j: dense vector search over chunks, dense vector search over tags, fulltext BM25 over chunks, fulltext BM25 over tag names.
  3. Resolve raw Chunk and Tag hits back to their parent entities via HAS_CHUNK and TAGGED traversals.
  4. Score and merge across all four paths โ€” dual-hit boost, chunk similarity, tag rarity.
  5. Proximity filter / boost using Neo4j's native point.distance() when a location is provided.
  6. Anonymous filtering โ€” Person entities are excluded from anonymous-user results.
  7. Return lean card data ( id , type , name , picture ) for fast rendering. Blurb generation is a separate endpoint ( POST /query/blurb ) that the frontend calls lazily for visible results only โ€” a one-hop graph expansion assembles structural context (owner, parent business, tags, location, profile excerpt) and passes it to a fast Claude Haiku model. LLM failures return {blurb: null} with 200 instead of failing the request.

Chunking, embedding, and AI tagging pipelines

Every create/update of a profile's markdown triggers:

  • Chunking on paragraph boundaries with a ~500-token target, preserving section headers as structural context for later LLM prompts.
  • Batched embedding in a single API call per profile.
  • AI tag assignment via Claude โ€” 3โ€“6 atomic, composable tags across domain and modifier dimensions.
  • Tag deduplication by embedding similarity (0.9 threshold) against existing tags, with Valkey-cached tag embeddings.
  • Full replacement semantics โ€” old chunks and tag edges are deleted and recreated inside a single Cypher transaction to guarantee atomicity.

Location geocoding and deduplication

Addresses are normalized through the Google Maps Geocoding API and deduplicated on the formatted_address string before a Location node is either reused or created. The same Neo4j point index powers proximity search in the directory today and is already sized for the "verified residence within a community boundary" traversal on the roadmap.

Authorization as Cypher

Every access check is a single traversal. Examples:

// Can this user modify this offer?
MATCH (p:Person {id: $userId})-[:OWNS|HAS_ACCESS]->(b:Business)-[:HAS_OFFER]->(o:Offer {id: $offerId})
RETURN p
// Is this contact attachable to this offer? (listings borrow contacts from parent)
MATCH (cc:ContactChannel {id: $contactId})<-[:OWNS_CONTACT]-(b:Business)-[:HAS_OFFER]->(o:Offer {id: $profileId})
RETURN cc

Two guards enforce the model: a global JwtGuard validates Supabase JWTs against the JWKS endpoint and auto-upserts an AnonymousVisitor node on first sight of an anonymous token; a RegisteredGuard rejects anonymous callers on write endpoints and on Person profile reads.

Privacy-controlled contact channels

Contact visibility is modeled on the edge , not the node. GET /contacts/:id?ref=<profile_id> resolves the CAN_CONTACT relationship from the referenced profile, reads its privacy property, applies the public vs registered rule against the caller's JWT, and โ€” on success โ€” logs a fire-and-forget HAS_CONTACTED edge. Owners always see their own contacts via OWNS_CONTACT regardless of attachment. The ownership constraint that listings can only attach contacts owned by their parent is enforced as a single traversal on every attach call.

Related items

A weighted, multi-signal relatedness engine with configurable weights:

  • Shared tags (graph match) โ€” 0.35
  • Tag embedding similarity (semantic neighbors of source tags) โ€” 0.25
  • Chunk embedding similarity (content-level match from top source chunks) โ€” 0.20
  • Ownership overlap โ€” 0.10
  • Proximity โ€” 0.10 Results are deduplicated across signals, scored, paginated, and returned as card data. Person entities are excluded for anonymous callers.

Test-driven, container-backed

Built strictly TDD across six phases โ€” foundation, core entities, supporting entities, pipelines, query engine, integration. Neo4j is spun up as a real @testcontainers/neo4j instance for every service and e2e test โ€” no mocking of Cypher โ€” so syntax and traversal errors surface at test time. External APIs (embeddings, LLM, Google Maps) are mocked behind injectable interfaces with deterministic vectors, which makes vector-similarity tests fully reproducible. Auth matrix coverage: every endpoint ร— (no token, anonymous, registered non-owner, registered owner, registered accessor).

Selected architectural decisions

  • Single-transaction writes for anything that touches chunks, tags, or relationships โ€” no partial entities on upstream failure.
  • Stateless service โ€” all state lives in Neo4j, all ephemeral state in Valkey.
  • Implicit view tracking โ€” HAS_VIEWED edges are created as fire-and-forget side effects of profile/media/link GETs, never blocking the response.
  • AnonymousVisitor as device, not person โ€” one device can sign in as many Persons over time; one Person can appear on many devices. SIGNED_IN_AS edges accumulate and are never overwritten, preserving pre-signup browsing history.
  • Cloudflare delivery URLs never appear in profile responses โ€” only thumbnail stubs. Full URLs require a dedicated GET, which also logs a view.
  • Valkey caches query and tag embeddings, not chunk embeddings โ€” chunks have near-zero reuse; queries and tag names converge quickly across users.

Why this foundation matters

The v1 Graph API isn't just a directory โ€” it's the seed of the full Eden vision. Each item on the roadmap is additive to this graph, not a replacement for it:

Now (v1 โ€” Graph API)Near futureFull vision
Person, Business, Offer, Ask profilesPayments between members and businesses"1% for Community" revenue model
AI-powered natural language searchVerified Repeat Patronage trackingAutomated loyalty and rewards
Privacy-controlled contact channelsCommunity and Group nodesCross-community group intelligence
Location-based discoveryVerified residenceCommunity governance tools
Anonymous browsing with session trackingNotification system (SMS, WhatsApp, email)Eden CIS โ€” full community intelligence
Media galleries and external linksRating and endorsement relationshipsPersonalized recommendations from patronage data

Payments become a PURCHASED edge. Verified Repeat Patronage becomes a pattern match on multiple PURCHASED edges between the same pair of nodes. Verified residence becomes a RESIDES_AT edge inside a COVERS boundary. Groups become nodes that own offers, asks, and tags. Every one of these is a new node type or relationship on the same graph โ€” the traversals I built today extend cleanly into every phase of the roadmap.

Impact

  • Delivered the foundation layer the entire Eden ecosystem is built on.
  • Hybrid GraphRAG search that returns results in ~200โ€“500ms and improves automatically as the community grows.
  • Authorization, privacy, and view tracking expressed uniformly as graph traversals โ€” fewer code paths, easier to audit.
  • A compounding data asset: each new person, business, offer, and transaction makes every existing node more findable and the network moat deeper.