Skip to content

Instantly share code, notes, and snippets.

@JofArnold
Last active May 23, 2025 22:42
Show Gist options
  • Save JofArnold/2173ccaaf207d43424edce0aaab28b0e to your computer and use it in GitHub Desktop.
Save JofArnold/2173ccaaf207d43424edce0aaab28b0e to your computer and use it in GitHub Desktop.

Consider a typical custom resolver with inline cypher

import { Driver } from 'neo4j-driver';

interface ResolverContext {
  driver: Driver;
}

export const myCustomResolver = async (
  _parent: unknown,
  args: unknown,
  context: ResolverContext,
): Promise<unknown> => {
  const { driver } = context;
  const session = driver.session({ database: process.env.NEO4J_DATABASE });

  const exampleCypherQuery = `
    MATCH (person:Person)-[:WORKS_AT]->(company:Company)
    RETURN person.name AS personName, 
       collect(DISTINCT company.name) AS companies
  `;

  const exampleParams = {
    etc: 'etc',
  };

  const res = await session.run(exampleCypherQuery, exampleParams);

  return res;
};

There is nothing that ensures this query is correct. For instance, I could have a typo WORDS_AT instead of WORKS_AT and I'd discover that at execution time.

The first solution I attempted was making custom parsers for my SDL to generate the relationship names (in a relationships.ts file) as part of my schema build step. So for instance I can do

import { Driver } from 'neo4j-driver';

import * as r from 'relationsips`

interface ResolverContext {
  driver: Driver;
}

export const myCustomResolver = async (
  _parent: unknown,
  args: unknown,
  context: ResolverContext,
): Promise<unknown> => {
   // ... etc

  const exampleCypherQuery = `
    MATCH (person:Person)-[:${r.Person.worksAt.type}]->(company:Company) // <--- here
    RETURN person.name AS personName, 
       collect(DISTINCT company.name) AS companies
  `;

   // etc
};

Obviously that still left open opportunities for error. For example this might not be a valid connection (accidentally typed Org instead of Company

So instead I progressed to generating functions that handle all the details.

const exampleCypherQuery = `
  MATCH ${r.Person.as("person").worksAt.Company.as("company")} // <-- here
  RETURN person.name AS personName, 
     collect(DISTINCT company.name) AS companies
`;

Slight inspiration from https://jawj.github.io/zapatos/

Note: I'm not thinking going all the way to Gremlin... But something that enables me to express what I want in Cypher in a type safe(r) way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment