-
-
Notifications
You must be signed in to change notification settings - Fork 569
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
Enhance error handling and expression in the GraphQL schema #533
Comments
@lifeiscontent You can extend the GraphQL schema to do that using the v4 alpha; install with https://github.com/postgraphql/graphql-build/tree/master/packages/graphql-build Let us know how you get on! |
@benjie if I worked on an apollo client for the example postgraph app would you help me fill in the blanks where needed? |
Sure 👍 |
@benjie I've started the project, feel free to track my work here: https://github.com/lifeiscontent/postgraphql-forum-example-apollo-client also, if you think I should move this into the postgraphql examples folder, I can do that as well, but I figured this might be a good example to have as a separate project. Let me know your thoughts 👍 |
I'm currently planning to move the examples and documentation out of this repository; so I definitely support it being it's own repo 👍 |
@benjie is there a reason why the authenticate method in the forum example returns this? mutation authenicate($input: AuthenticateInput!) {
authenticate(input: $input) {
jwtToken {
role
personId
}
}
} I would've expected a token string and not a role and personId, should I update the sql in the forum example? |
The reason for this is that PostGraphQL receives this composite type and then signs it to transform it into a JWT token itself - we do not post the JWT secret into the database for the database to perform the signing. See |
@benjie is there a cli option for that? |
ah, found it. --token that's confusing |
@benjie I implemented login, and authorizations today. Will continue to work on it tomorrow. |
Awesome 👏 |
@benjie how would you create a post as the currentPerson? I'm wondering if I should hit the graphql endpoint and store the result of the currentPerson query in the apollo cache, and then just ask for currentPerson each time, but that seems inefficient, any suggestions? |
That's what I tend to do. An alternative is to do it through a |
@benjie alright, post creation is working now. This is where I need your help: https://github.com/lifeiscontent/postgraphql-forum-example-apollo-client/blob/master/src/containers/CreatePostFormContainer.js on line 14, you'll notice the |
Regarding the errors this is kind of an open issue. There's been some work on improving this with #513 but I think it's still quite manual - having to extract what went wrong from the error itself. I'm not actually sure how best to handle this. Regarding the topic ENUM, you could use GraphQL introspection to do this: query IntrospectEnums {
__schema {
types {
kind
name
description
enumValues {
name
description
isDeprecated
}
}
}
} I'm afraid you'll have to filter it client side, there doesn't seem to be support for only getting the enum types. const myEnumDetails = data.___schema.types.find(
({kind, name}) => kind === 'ENUM' && name === 'MyEnumName'
); |
@benjie ah, okay great. I guess I'll wait till the merge. Looking forward to it 👍 |
@benjie when will the extended errors be published? should I use |
I'll probably publish to @latest on Tuesday as 3.4.0; if I can merge it to @next at the same time I'll also release that. |
@benjie okay, sounds good. I got the introspection enum on the post working 👍 If you have a moment, I'd love for you to take a look at the code and see if I'm doing anything dumb. https://github.com/lifeiscontent/postgraphql-forum-example-apollo-client/pull/1/files just so you can have an idea of where I want to take this, I'd like to provide an example to the community that would be considered a production ready application using postgraphql. |
LGTM; few comments:
|
if you can answer these for me, I don't mind documenting the readme with your answers if you want. so for the unfinished todos, can you explain a bit more? for the SQL Schema, how do I go about adding that to the project/change it? for the jwtToken, the current process is, check localStorage, if it exists, setup the middleware for apollo, setup the apollo client, and setup the redux store, once the store is setup dispatch the SIGN_IN action and tell the store the user is signed in. if I store the token in redux first, how do you suggest I tell apollo to use the middleware/call the redux store if it's not setup? I'm not sure what isLocalhost is, this is my first time hearing about it, do you have any reading material on where I can figure out how to set that up? |
It's up to you if you feel migrations are inside or outside the scope of this project. Personally I'd err on the side of "outside" and just put the schema in
I'm not sure exactly; I would just make sure Apollo's network layer has access to the redux store so it can pull data out of it and then the network layer would just say "if JWT then authenticate otherwise don't". You could then set up the store and once that's set up issue the SIGN_IN based on the contents of
It's from the service worker create-react-app created for you, it's just a boolean that determines if window.location.host looks like localhost or not (i.e. is it development or not). |
@benjie okay, so currently I'm using the proxy option within create-react-app to proxy to the postgraphql server, is there a better way to connect to postgraph? because using this option, it's making it difficult to connect to .meh,.local,.dev domains. You can see my current changes here: https://github.com/lifeiscontent/postgraphql-forum-example-apollo-client/pull/1/commits/96d4965c6289535bf61986620199dcfa3a320d89 |
It's much better to use CORS than to proxy - the JWT is your source of security so using CORS on a separate server helps solve security issues such as CSRF since the CSRF attacks would not contain the JWT token (since it's not in cookies) and thus cannot do anything harmful. |
Though having glanced at your code now you're not doing anything funky, so your proxying should be fine security-wise. I found with create-react-app that the proxy option didn't work very well with PostGraphQL, so instead I flipped it on its head and ran an express server which handled the routes I wanted (/graphql, /graphiql, /auth/*, etc) and then proxied the remainder into create-react-app (rather than the other way around - CRA has some annoying rules for it's proxying (such as looking for |
@benjie so do you think I should enable cors? It says in the CLI docs that we should use a proxy if possible. https://github.com/postgraphql/postgraphql/blob/master/docs/cli.md The only problem with the proxy in CRA is it doesn't work for *.dev, etc domains only localhost. |
@benjie also in terms of migrations, I'm still trying to understand how to build a good workflow between postgraphql and graphql schemas. Is there a way to go from graphql schema to -> postgraphql? or would you just write the raw sql schema and have postgraphql create the graphql representation? I think there are parts of graphql that I'm not completely understanding to bridge the gap in my knowledge. Would really appreciate it if you could shed some light here as again, it'll benefit the community for learning purposes and easier onboarding for potential postgraph users. |
Personally I would build an express server for PostGraphQL and then proxy through to the create-react-app code; this then means that on production you just switch out the "proxy through to create-react-app" with "serve the static folder containing built assets".
The core feature of PostGraphQL is that it builds a GraphQL API based on your PostgreSQL schema, so I would just worry about the PostgreSQL schema and let PostGraphQL handle the rest. Have you had a look through the docs I'm building for Graphile (the core of PostGraphQL v4)? https://www.graphile.org/ To extend the schema you can pass additional |
@benjie I guess I'm still a bit confused on what exactly the purpose of graphile is, but I'm interested in knowing more. Also, I just implemented a update_password function for the forum_example project. can you give feedback on the SQL? I'm not sure if this is something you'd expect and apparently GraphQL wants to return the field as create function forum_example.update_password(
email text,
previous_password text,
password text,
password_confirmation text
) returns boolean as $$
declare
account forum_example_private.person_account;
begin
select a.* into account
from forum_example_private.person_account as a
where a.email = $1;
if account.password_hash = crypt(previous_password, account.password_hash) and password = password_confirmation then
UPDATE forum_example_private.person_account set password_hash = crypt(password, gen_salt('bf')) where forum_example_private.person_account.email = $1;
return true;
else
return false;
end if;
end;
$$ language plpgsql strict security definer;
comment on function forum_example.update_password(text, text, text, text) is 'Updates the password of a person.'; |
It's basically a way of extending a GraphQL API using plugins; the entire of the GraphQL schema in PostGraphQL v4 is generated through a number of plugins: some do introspection, some add connection types, some add table types, some add the relevant columns to those types, some add the
The update_password function is marked as I don't think the mutation needs
Other than that, looks good 👍 |
We don't currently support anonymous types: Create a named type instead and then return that. |
@benjie ah okay, thanks. so I'm almost finished wrapping up the "Edit Account" feature, but I just ran into another issue that I'm not sure how to handle. In Postgraphql the updatePerson method expects a nodeId and not the Id of the actual person, how would I get this information? Or is this a bug? |
Use |
@benjie so I just looked at the extendedErrors option, and it still seems a bit simplistic for a production app, unless I'm not understanding how I can use these errors to then map to my forms to let users know what they didn't fill in correctly. Would you recommend using graphile instead of postgraphql? or would you want to help me power through these problems so postgraphql is a more robust so it can handle real world use cases? I'm currently confused in terms of which direction I should go, eventually, after I'm done building this project I plan to use what I learn from you and potentially the postgraphql community to have postgraph power my company's own app. I just need to understand what the current limitations are and how to work around them in a reasonable manner. |
PostGraphQL v4 is mostly just a wrapper around graphile; so you'll still be using PostGraphQL. I definitely want to make PostGraphQL the best it can be; what do you need from the errors to be more useful for your project? An example of what it gives currently vs what you would like would be really useful - then we can try and figure out how to get that data from PostgreSQL into your app via PostGraphQL. This is just the first punt at |
If it turns out we can't get the error information you need from PostgreSQL directly then that's where an app-specific plugin may come in (e.g. first layer of validation in Node.js rather than the DB?). |
@benjie man I love how enthusiastic you are about this stuff, it's really inspiring. I'll put together some error messages when I wake up tomorrow and explain the use cases I'm thinking of 👍 Thanks again for all the hard work |
@benjie Hi again, sorry about being M.I.A. for the last 2 days. I'm back in, working on the app. first question I'm curious about, if I wanted to be able to return the authenticated user with the authenticate method in the example forum app, how would I go about doing something like that? or is this even possible currently? at the very least, the data requirements of a typical app is the token, and the id of the current user. create function forum_example.authenticate(
email text,
password text
) returns forum_example.jwt_token as $$
declare
account forum_example_private.person_account;
begin
select a.* into account
from forum_example_private.person_account as a
where a.email = $1;
if account.password_hash = crypt(password, account.password_hash) then
return ('forum_example_person', account.person_id)::forum_example.jwt_token;
else
return null;
end if;
end; |
So I'd do it like this: mutation Authenticate($email: String, $password: String) {
authenticate(email: $email, password: $password) {
jwtToken
query {
currentUser {
id
name
}
}
}
} (Assuming you have a The caveat with this is that your authenticate method will need to |
@benjie when I update the method like so, I get an error. create function forum_example.authenticate(
email text,
password text
) returns forum_example.jwt_token as $$
declare
account forum_example_private.person_account;
begin
select a.* into account
from forum_example_private.person_account as a
where a.email = $1;
if account.password_hash = crypt(password, account.password_hash) then
select set_config('jwt.claims.person_id', account.person_id, true);
return ('forum_example_person', account.person_id)::forum_example.jwt_token;
else
return null;
end if;
end;
$$ language plpgsql strict security definer; |
@benjie sorry, this questions probably pretty basic, I just figure you've already dealt with a lot of these problems. |
got it. create function forum_example.authenticate(
email text,
password text
) returns forum_example.jwt_token as $$
declare
account forum_example_private.person_account;
begin
select a.* into account
from forum_example_private.person_account as a
where a.email = $1;
if account.password_hash = crypt(password, account.password_hash) then
perform set_config('jwt.claims.person_id', account.person_id::text, true);
return ('forum_example_person', account.person_id)::forum_example.jwt_token;
else
return null;
end if;
end;
$$ language plpgsql strict security definer; |
Oh yeah, sorry I forgot you have to change For future issues, when reporting a bug "I get an error" is not useful - tell us what the error is 🙂 |
@benjie I've been looking through the docs for apollo, so I'm trying to optimize the app as far as I can without bugging you, and then when I'm ready, hopefully I'll have some solid feedback for you 😄 |
@benjie just fixed all the mutations to update the cache properly, next is error messages! Would love another review when you have time. https://github.com/lifeiscontent/postgraphql-forum-example-apollo-client/pull/1/files |
@benjie so here's what I've been thinking about in terms of an interface for errors. you should be able to query it within mutations/queries. for example: query AllPostsQuery {
allPosts {
nodes {
title
}
errors {
field
type
}
}
} where errors is. type Errors = {
field: String
type: ErrorType!
} errors.field will return null if there is an error that isn't related to a field. I don't suggest having a message field so that users can agnostically create the message on the client using I18n. and let me know what you think about this, I can also create a seperate issue if you want for this topic in case others have opinions on the matter, but I think this would be sufficient for rendering the proper messages to the client so a user can know how to proceed. |
@benji any updates here? If there is something I can help with, I wouldn't mind contributing, but I'm still very new to the codebase so guidance would be helpful if you don't have the time to focus on this. 👍 |
Sorry for the delay, I do this work in my spare time and wanted to make sure I had a solid understanding of the best practices before replying. I had a read through graphql/graphql-js#560 (also graphql/graphql.github.io#204) and a number of other resources on the web and the consensus seems to be that sometimes modelling the errors as part of the data of the query (as you've done above) can be the best way 👍 How do you think we would implement this? I'd love to see some examples - input data, executed SQL, error thrown/caught, how that is output. I'm not sure how this would work with queries as you have suggested, but for mutations if it's validation errors / unique constraint conflicts I can imagine it working. Rather than having a generic
I hear you regarding not having This definitely sounds like quite an undertaking - please do open a new issue about it. |
@benjie sorry for the silence here, we shifted gears, now we're back on the grind for using postgraphile. I was wondering if there has been any movement in error handling since our last talk. |
Afraid not; however there is a powerful plugin interface now, so expanding the schema with error features is feasible. |
@benjie any plans to do some documentation on plugins? I'd love to jump in but I feel the docs are a bit bare so I'm not sure where to start. On another note, how much would you charge to implement something like this? |
Better docs for graphile-build (and some helpers to make it easier!) are on my todo list, but PostGraphile itself is my focus right now so it'll be a while before I get around to it. I'm working on trying to get more income so I can spend more time working on PostGraphile / graphile-build. Could you supply a detailed spec of what you're after? Also have you seen @pyramation's work in #723 ? |
https://www.graphile.org/postgraphile/extending/#the-easy-way-graphile-utils I'm sticking with using GraphQL errors, but do note that we now expose the I'm going to close this issue for now, but please feel free to keep discussing below. Reading back over this was pretty eye-opening and very valuable feedback - thank you for taking the time to engage with me! 👍 |
Hey there,
I was wondering how I could use postgraphql with relay that has a bit of app state as well?
it looks like what facebook does with modern is build a graphql endpoint that just doesn't do anything to the database, but then the cache picks it up, so how would this work within postgraphql?
The text was updated successfully, but these errors were encountered: