Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Form validation #179

Closed
xpepermint opened this issue Sep 18, 2015 · 6 comments
Closed

Question: Form validation #179

xpepermint opened this issue Sep 18, 2015 · 6 comments

Comments

@xpepermint
Copy link

Let's say you have a form which posts data to API server. The API server validates the input and returns JSON object. If the input is invalid an error objects {errors: {field1: "is required"}} is returned. How do we handle and serve these kind of errors when using GraphQL? How and where should data validation be implemented (should that be part of my GraphQL code, maybe inside each resolve function)?

@leebyron
Copy link
Contributor

My suggestion is to first perform form validation on the client. You'll be able to develop a much nicer user interface where invalidations are seen immediately upon typing and contextual advice for resolving the issue can be presented.

Of course, it's also best to have form validation on the server as well to avoid performing actions on invalid data (though I would not recommend using a server error response as a way to update a UI). There are two ways of going about this:

  • Strong typed input values. GraphQL input types are strongly typed just as output types are. That means you can define input objects where each field is required to be of a certain type and if it is required or not. GraphQL will then perform validation before executing a query, ensuring the input values adhere to the types defined. If an invalid value is presented, GraphQL will respond with an error describing why.
  • Throwing errors within resolve. Regardless of input type, GraphQL resolve functions may throw an error which GraphQL will capture and place in the errors field of the response JSON. You can have this error message read however you like, including including machine-readable information if that's valuable.

@grydstedt
Copy link

@leebyron At FB, do you define specific types for say emails, a GraphQLEmail?

@leebyron
Copy link
Contributor

You mean as custom scalar types? We have a couple that we define, but actually for the most part we haven't needed them. I think one of the more interesting ones which we may end up defining in the spec is DateTime.

@xpepermint
Copy link
Author

@leebyron Thanks, just as I thought! And yes, I also wonder the same as @grydstedt. How do you validate a field (e.g. an email)?

@mattkrick
Copy link
Contributor

Would be great if graphql would also serialize a thrown object that isn't an Error. An example might be a signup form that errors when the user exists. You'll want an overarching error Cannot create account and one specific to the email field in your form Email address already exists. The current pattern of stringifying an object works, but sticking that in the message means I gotta perform a check on the value of message and parse if needed. A more flexible errors object would be a big help!

@leebenson
Copy link

I ran into this same issue.

Coming from a legacy REST app, I used boom to generate errors that conformed to HTTP response codes, and used a universal schema (shared on the server and the client) to do form validation, so I wasn't repeating keystrokes.

If validation failed, it would wind up being thrown like this...

throw boom.badRequest('Validation error', validationObject);

... where validationObject would be a form field/error message object like this:

{
  email: "Looks like an account already exists. Login instead!",
  password: 'Your password should be a minimum of 8 characters.  You entered 3.',
  country: 'Please select a valid country'
}

It was useful to share validation logic on the front and back, because:

  1. The UI could be updated with error codes without a round trip to the server (most of the time)
  2. Using isomorphic-fetch alongside my Redux actions meant I could validate anywhere, and call the same REST endpoint
  3. I needed to re-check everything on the server anyway, so it might as well be the same logic
  4. There were occasionally edge cases that required secondary server checks. Like if a user chose a username that was valid at the time of submission, but in the split-second between the user submitting and the server checking, it had been snapped up elsewhere.

In those cases, I wanted the server to respond with the same error format as used on the front-end, so it could be immediately compatible.

To get that same concept back in GraphQL, I'm currently trying out graphql-errors (a guide for using it is available here)

By default, it sends back a plain 'Internal Error' message, but there's a setDefaultHandler method that can be used to override the message. I'm using this to 'listen' to my boom errors, read the status code and the data key, and send back slightly more descriptive messages using the same form field/error message format.

Hope that helps someone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants