Search code examples
neo4jcypherserverless

Serverless Framework + Neo4j


I'm running into issues with connection and disconnecting to my Neo4j database using the Serverless Framework. In the Neo4j docs, it shows this code in the JS section:

const neo4j = require('neo4j-driver')

const driver = neo4j.driver(uri, neo4j.auth.basic(user, password))
const session = driver.session()
const personName = 'Alice'

try {
  const result = await session.run(
    'CREATE (a:Person {name: $name}) RETURN a',
    { name: personName }
  )

  const singleRecord = result.records[0]
  const node = singleRecord.get(0)

  console.log(node.properties.name)
} finally {
  await session.close()
}

// on application exit:
await driver.close()

I run into issues with this code for a couple reasons. In my lambda function, if I run just this code I'll get the error:

Pool is closed, it is no more able to serve requests

If I move the driver.close into the finally, I'll get this error:

Neo4jError: Cannot begin a transaction on a closed session.

To be clear, I can make one successful request to the database then the second will fail.

I had this issue when using pg-node with postgres + serverless. I fixed the issue by using the serverless-postgres npm package.

It seems that maybe I need to not use the driver and instead make simple HTTP request like the docs here. Interested in opinions on how to move forward.


Solution

  • I'm not using Lambda, but I believe for any JS runtime the same rules would apply. In the example below, included some of the driver's option for you to tweak and see what combination works for your scenario.

    Can you try:

    import { driver, auth } from 'neo4j-driver';
    import { config } from 'dotenv';
    
    /**
     * Read environment variables (for test-bed)
     */
    config();
    
    /**
     * Singleton for Neo4j Connection Driver
     */
    const neo4j = driver(process.env.NEO4J_URI, auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD),
      {
        maxConnectionLifetime: 10 * 60 * 1000, // 10 minutes
        maxConnectionPoolSize: 300,
        encrypted: process.env.NEO4J_ENCRYPTION || "ENCRYPTION_ON",
        trust: "TRUST_ALL_CERTIFICATES",
        // trustedCertificates: [process.env.NEO4J_TRUSTED_CERTS],
        logging: {
          level: 'debug',
          logger: (level, message) => console.log('+++' + level + ' ' + message)
        }
      });
    
    
    /**
     * Wrapper to execute cypher queries using session from pool 
     * and automatic transaction management (commit, rollback, session closing)
     * @param {*} query Cypher statement(s) 
     * @param {*} params Query params 
     * @returns Promise<Record<Dict, PropertyKey, Dict<PropertyKey, number>>[]>
     */
    async function runNeo4jQuery(query, params) {
      const session = neo4j.session();
      let result;
    
      try {
        const transaction = session.beginTransaction();
    
        try {
          result = await transaction.run(query, params);
          await transaction.commit();
    
          return result.records;
        } catch (error) {
          await transaction.rollback();
          throw error;
        }
      } finally {
        await session.close();
      }
    }
    
    async function main() {
        let results = [];
    
        const statement = `CREATE (a:Person {name: $name}) RETURN a`
    
        let params = {
            name: 'Alice'
        }
    
        results = await runNeo4jQuery(statement, params);
        console.debug(results);
    }
    
    (async function () {
        await main();
    })().catch(e => {
        console.error(e);
        process.exit(1);
    }).finally(async () => {
        await neo4j.close();
        process.exit(0);
    }