Graph (Neo4j)
The API uses Neo4j for graph database operations, handling relationships between entities.
Overview
Neo4j stores:
- User relationships (follows, friends)
- Book relationships (similar, recommended)
- Reading activity graphs
- Social connections
CypherBuilder
The CypherBuilder class provides type-safe Cypher query construction.
typescript
import { CypherBuilder } from "./CypherBuilder"
const query = new CypherBuilder()
.match("(u:User {id: $userId})")
.match("(u)-[:FOLLOWS]->(f:User)")
.return("f")
.build()GraphRepository
Base class for Neo4j operations:
typescript
export class UserGraphRepository extends GraphRepository<User> {
async findFollowers(userId: string): Promise<User[]> {
const cypher = new CypherBuilder()
.match("(u:User {id: $userId})")
.match("(follower:User)-[:FOLLOWS]->(u)")
.return("follower")
.build()
return this.run(cypher, { userId })
}
async follow(followerId: string, followeeId: string): Promise<void> {
const cypher = new CypherBuilder()
.match("(follower:User {id: $followerId})")
.match("(followee:User {id: $followeeId})")
.merge("(follower)-[:FOLLOWS]->(followee)")
.build()
await this.run(cypher, { followerId, followeeId })
}
}Common Patterns
Creating Nodes
typescript
async createNode(book: Book): Promise<void> {
const cypher = new CypherBuilder()
.create("(b:Book $props)")
.build()
await this.run(cypher, {
props: { id: book.id, title: book.title },
})
}Creating Relationships
typescript
async addToShelf(userId: string, bookId: string): Promise<void> {
const cypher = new CypherBuilder()
.match("(u:User {id: $userId})")
.match("(b:Book {id: $bookId})")
.merge("(u)-[:HAS_ON_SHELF]->(b)")
.build()
await this.run(cypher, { userId, bookId })
}Finding Paths
typescript
async findRecommendations(userId: string): Promise<Book[]> {
const cypher = new CypherBuilder()
.match("(u:User {id: $userId})-[:FOLLOWS]->(friend:User)")
.match("(friend)-[:HAS_ON_SHELF]->(b:Book)")
.where("NOT (u)-[:HAS_ON_SHELF]->(b)")
.return("DISTINCT b")
.limit(10)
.build()
return this.run(cypher, { userId })
}Connection Management
Neo4j connection is managed via dependency injection:
typescript
// bindings.ts
Container.bind(Driver).factory(() => neo4jDriver)
// Repository
constructor(@Inject private driver: Driver) {
super(driver)
}Testing
Mock the driver for unit tests:
typescript
const mockSession = {
run: vi.fn().mockResolvedValue({ records: [] }),
close: vi.fn(),
}
const mockDriver = {
session: vi.fn().mockReturnValue(mockSession),
}