1
votes

I'm using NodeJS 10.16.2, Neo4j 3.5.6, neo4j-driver v1, Express, EJS

My goal is to create a relationship of either a 1:1 or 1:Many for objects that previously exist in a Neo4j datastore. Example: Create (b:Beer)-[:BREWED_WITH]->(h:Hop). This relationship is created for either 1 beer and 1 hop, or 1 beer and multiple selected hops (the code below will explain more). At this point in time, the 1 Beer to 1 Hop works no problem, but the 1 Beer to Many Hops does not work.

Here are all the necessary code parts...

server.js This code gets the data...

app.get('/', async (req, res) => {
  try { 
    //paring this Beer example down from its usual
    const beerResult = await session.run(`MATCH (b:Beer) RETURN b`); 
    const beerArr = beerResult.records.map(({_fields}) => {
      return {name:_fields[0]};
    });

    const hopResult = await session.run('MATCH(h:Hop) RETURN h Order By h.name ASC');
    const hopArr = hopResult.records.map(({_fields}) => {
      const {identity, properties} = _fields[0];
      return {id: identity.low, name: properties.name};
    });

    res.render('index', {
      beer: beerArr,
      hop:hopArr
    });
  } catch(e) {
    console.log("Something went wrong", e)
  }
});

index.ejs This code displays the data in a form...

     <h2>Beer->Hop</h2>
          <form  method="post" action="/beerhop/add">
               <label>Beer Name</label><br>
               <select class="form-control" type="text" name="beername">
                    <% beer.forEach(function(beer){ %>
                         <option><%= beer.name %></option>
                    <% }) %>
               </select><br>

               <label>Hop Name</label><br>
               <select class="form-control" type="text" name="hopname" multiple>
                    <% hop.forEach(function(hop){ %>
                         <option><%= hop.name %></option>
                    <% }) %>
                </select><br>                
                <input class="button" type="submit" value="Submit">
          </form>

server.js This code contains the query that posts to the Neo4j database...

app.post('/beerhop/add',async (req, res) => {
  const {beername, hopname} = req.body;
  try {
    const result = await session.run(`Match (b:Beer {name: $beernameParam})
                                      Match (h:Hop {name: $hopnameParam})
                                      Create (h)<-[r:BREWED_WITH]-(b)
                                      Return h.name,b.name`, 
                                      {hopnameParam:hopname, beernameParam:beername});
    if (result) {
      res.redirect('/');
      console.log(hopname);
      session.close()
    }
  } catch (e) {
    console.log("Something went wrong", e)
  };
});

I realize there are many different ways to Create relationships for existing nodes in Neo4j. The above query is the drop dead easiest.

What Works

If I select 1 Beer and 1 Hop from the form and Submit, the relationship is created in Neo4j.

What Doesn't Work

If I select 1 Beer and 2+ Hops from the form and Submit, the relationships are not created in Neo4j. Please note that running this neo4j query

Match (b:Beer {name: 'Disco Wolf'})
Match (h:Hop)
Where h.name IN ['Citra', 'Strata', 'Idaho7']
Create (b)-[:BREWED_WITH]->(h)
Return b,h

directly in the Neo4j Browser works to make the multiple relationships, BUT it does not work from my app.

I have also tried a FOREACH statement with my Neo4j query,

Match (b:Beer {name: $beernameParam}), (h:Hop {name: $hopnameParam})
With b as beer, collect(h) AS hops
FOREACH (hop IN hops | Create (beer)-[:BREWED_WITH]->(hop))
Return beer,hops

it also does not work in my app (but is a viable query in Neo Browser).

In both cases,console.log(hopname); will show either the 1 hop as a single name: Galaxy, or the multiple hops in an array: [ 'Citra', 'Strata', 'Idaho7' ].

Any idea how I can get the 1 Beer to Many Hops relationships created?

Many Thanks,

r

1

1 Answers

2
votes

You can make sure that you always pass a list of hop names to the query:

...
const result = await session.run(`MATCH (b:Beer), (h:Hop)
                                  WHERE b.name = $beername AND h.name IN $hopnames
                                  CREATE (h)<-[r:BREWED_WITH]-(b)
                                  RETURN h.name, b.name`, 
                                  {hopnames: Array.isArray(hopname) ? hopname : [hopname], beername: beername});
...

By the way, you may want to use MERGE instead of CREATE to avoid creating duplicate relationships.