neo4j / graphql

A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations.
https://neo4j.com/docs/graphql-manual/current/
Apache License 2.0
504 stars 149 forks source link

Integrate with graphql-code-generator and validation libraries #1550

Closed corysimmons closed 2 years ago

corysimmons commented 2 years ago

Is your feature request related to a problem? Please describe. I would love to use my Neo4j GraphQL schemas as the single source of truth for all my frontend forms as well as anything that can go into the db.

Currently I have to:

Describe the solution you'd like It would be amazing if Neo4j's GraphQL offering just tightly integrated with a modern validation lib like Zod so I could write my schema files in one place like so:

input ExampleInput {
  email: String! @required(msg: "Hello, World!") @constraint(minLength: 50, format: "email")
  message: String! @constraint(startsWith: "Hello")
}

And these constraints would be valid in the db, and I could use the generated type in my frontend forms.

Describe alternatives you've considered

I've tried to use a few libraries to do something similar to this:

Additional context

It seems like there is a big lack of modern validation going on with Neo4j (it's really something that has me worried about diving in). I think you can do it with Cypher and creating constraints, but even then you have to be on an "Enterprise" plan to get relationship constraints?

Is there any way for the little startup to get relationship constraints?

Is there any hope of a clean (something like Zod) way to add validation/constraints to our graphql schemas that are honored by the database?

darrellwarde commented 2 years ago

We love this idea and it's been on our minds for a long time!

If we were to implement this in the library, I think constraints applied at the GraphQL make the most sense - essentially input validation on Mutations which would modify the database, and throwing an error if the input violates the constraints.

It might not make it's way into our planned work for a little while due to competing demands, however, we always welcome community collaboration if a contributor wants to pick this up!

corysimmons commented 2 years ago

Maybe. I'm on the fence about where validation should live.

I think it'd be better as Zod models that simply worked with Neo4j GraphQL. There are already tons of integrations with these validation libraries and other stuff. For instance check out this library where you can make a Zod model and then just pass it into react-hook-form to easily create clientside errors https://github.com/react-hook-form/resolvers and this library to get backend Express validation from the same Zod model https://www.npmjs.com/package/zod-express-middleware

If there was a way to do something like

import {SomeZodModel} from '~/YourZodModels'

export default gql`
input SomeNeo4jType {
  email: String! @validationModel(SomeZodModel)
}
`

And use that same validation model in other places:

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

import {SomeZodModel} from '~/YourZodModels'

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(SomeZodModel),
  });

  return (
    <form onSubmit={handleSubmit((d) => console.log(d))}>
      <input {...register('name')} />
      {errors.name?.message && <p>{errors.name?.message}</p>}
      <input type="submit" />
    </form>
  );
};

That would be amazing.

Maybe Neo4j GraphQL could just straight-up copy some open-source and package it up as part of core Neo4j GraphQL to offer the same functionality (and make some integrations with things like react-hook-form, Express, etc), but built into the GraphQL type? I'm not sure how that'd look and it seems like more work than simply

email: String! @validationModel(SomeZodModel)`

What are your thoughts? If we can figure this out and I can get some guidance I might be up for working on this.

darrellwarde commented 2 years ago

There's some good ideas here for an eventual solution, but going to close this in favour of #138 which has been around for (a lot) longer and really the principal is the same. Thanks!