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.
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_CONTACTrelationship 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_CONTACTfor ownership,CAN_CONTACT {privacy}for per-profile attachment โ the same contact node can bepublicon one profile andregisteredon another - Behavioral:
HAS_VIEWED,HAS_CONTACTED,SIGNED_IN_ASBacked 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:
- Embed the user query โ Valkey cache hit-or-miss against OpenAI
text-embedding-3-small. - Four parallel retrievals against Neo4j: dense vector search over chunks, dense vector search over tags, fulltext BM25 over chunks, fulltext BM25 over tag names.
- Resolve raw Chunk and Tag hits back to their parent entities via
HAS_CHUNKandTAGGEDtraversals. - Score and merge across all four paths โ dual-hit boost, chunk similarity, tag rarity.
- Proximity filter / boost using Neo4j's native
point.distance()when a location is provided. - Anonymous filtering โ Person entities are excluded from anonymous-user results.
- 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_VIEWEDedges 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_ASedges 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 future | Full vision |
|---|---|---|
| Person, Business, Offer, Ask profiles | Payments between members and businesses | "1% for Community" revenue model |
| AI-powered natural language search | Verified Repeat Patronage tracking | Automated loyalty and rewards |
| Privacy-controlled contact channels | Community and Group nodes | Cross-community group intelligence |
| Location-based discovery | Verified residence | Community governance tools |
| Anonymous browsing with session tracking | Notification system (SMS, WhatsApp, email) | Eden CIS โ full community intelligence |
| Media galleries and external links | Rating and endorsement relationships | Personalized 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.