Overload GraphQL Schema to Support getting a user by username or password - graphql

I am trying to create a GraphQL Schema such that a user will be able to find a user by their email or username. Imagine for these purposes one user could have an email that is the username of another user.
My typeDefs look as follows:
const typeDefs = gql`
type Query {
user: User(username: String!)
user: User(email: String!)
}
`;
Is this a valid Schema? If not how would I change my solution to be valid and solve my problem?
Edit: So I tried to execute the above and I get an error: 'Error: Field "Query.user" can only be defined once.' As I thought I might.
Is there any way to ensure that exactly one of username and email is null in the following: user: User(username: String, email: String)?

There is no "overloading" of fields in GraphQL, and no syntax that would support one of two fields being required. An argument is either nullable or non-null, that's it. You can do this:
type Query {
user(username: String, email: String): User
}
or this
type Query {
user(filter: UserFilter!): User
}
input UserFilter {
username: String
email: String
}
and then handle validation (at least one argument is defined but not both) inside your resolver. The only other way to do it would be something like:
type Query {
user(key: UserFilterKey!, value: String!): User
}
enum UserFilterKey {
username
email
}
But this kind of approach is not very intuitive for anyone consuming the schema. It also only works if the arguments in question have the same type.

Related

How to update single data when using Fauna and GraphQL

I have some problems when using Fauna and GraphQL
When I use GraphQL to update data, all parameters are required
For example
// Schema
type User {
username: String!
password: String!
phone: String!
}
// Mutation
mutation {
updateUser(id: "xxxxx", {"phone": "+886 110220330"}){
username,
phone
}
}
// Error
But I only want to update the phone data this time, how can I skip other parameters?
Although it can solve the problem when I delete the Not null of all attributes in User type,
But it doesn't look like a good way and safe
// after
// Schema
type User {
username: String
password: String
phone: String
}
There is a preview feature that you can use by adding a header to your GraphQL query.
X-Schema-Preview: partial-update-mutation
Once you do this, you will have access to a new mutation called partialUpdateUser which will make all of the input values optional.
See the docs for more information: https://docs.fauna.com/fauna/current/api/graphql/previews/

GraphQL variable combination validation

Imagine the following (simplified) GraphQL schema:
type User {
username: String
email: String
}
type Query {
user(username: String, email: String): User
}
If I would only want to allow querying user by giving a username, I would of course change it to user(username: String!), making the username required using the exclamation mark. Same thing vice versa with the email.
Is it possible though to have a GraphQL native solution where I validate for the existence of only either one (logical XOR) or at least one (logical OR) of the two input parameters?
Of course I could do it in the query resolver manually, but a #constraint directive like it is being used in Apollo GraphQL spreading across variables would be nice.
Directly ... not possible:
check fields existence [within args] on resolver;
use #constraint to check each field shape;
Indirectly:
you can try to use union of input types:
.
type UserNameInput {
username: String!
}
type UserEmailInput {
email: String!
}
type UserInput = UserNameInput | UserEmailInput
type Query {
user(input: UserInput): User
}

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

Include relationship when querying node using Prisma generated wrapper

I am following the GraphQL Prisma Typescript example provided by Prisma and created a simple data model, generated the code for the Prisma client and resolvers, etc.
My data model includes the following nodes:
type User {
id: ID! #unique
displayName: String!
}
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User!
}
I've seeded with a system user and user.
mutation {
systemUserLogin: createSystemUserLogin({
data: {
username: "SYSTEM",
passwordEnvironmentVariable: "SYSTEM_PASSWORD",
user: {
create: {
displayName: "System User"
}
}
}
})
}
I've created a sample mutation login:
login: async (_parent, { username, password }, ctx) => {
let user
const systemUser = await ctx.db.systemUserLogin({ username })
const valid = systemUser && systemUser.passwordEnvironmentVariable && process.env[systemUser.passwordEnvironmentVariable] &&(process.env[systemUser.passwordEnvironmentVariable] === password)
if (valid) {
user = systemUser.user // this is always undefined!
}
if (!valid || !user) {
throw new Error('Invalid Credentials')
}
const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET)
return {
token,
user: ctx.db.user({ id: user.id }),
}
},
But no matter what I do, systemUser.user is ALWAYS undefined!
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
But how can I tell it that I want to include the User relationship?
Edit: I tried the suggestion below to use prisma-client.
But none of my resolvers ever seem to get called...
export const SystemUserLogin: SystemUserLoginResolvers.Type<TypeMap> = {
id: parent => parent.id,
user: (parent, args, ctx: any) => {
console.log('resolving')
return ctx.db.systemUserLogin({id: parent.id}).user()
},
environmentVariable: parent => parent.environmentVariable,
systemUsername: parent => parent.systemUsername,
createdAt: parent => parent.createdAt,
updatedAt: parent => parent.updatedAt
};
And...
let identity: UserParent;
const systemUserLogins = await context.db.systemUserLogins({
where: {
systemUsername: user,
}
});
const systemUserLogin = (systemUserLogins) ? systemUserLogins[0] : null ;
if (systemUserLogin && systemUserLogin.environmentVariable && process.env[systemUserLogin.environmentVariable] && process.env[systemUserLogin.environmentVariable] === password) {
console.log('should login!')
identity = systemUserLogin.user; // still null
}
Edit 2: Here is the repository
https://github.com/jshin47/annotorious/tree/master/server
There are currently two ways to solve this problem:
Using the Prisma client as OP does at the moment
Using Prisma bindings as was suggested by #User97 in the accepted answer
You can learn more about the difference between Prisma client and Prisma bindings in this forum post.
As OP is currently using Prisma client, I'll use it for this answer as well!
Let's take a look at a statement OP made in the question:
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
OP stated correctly that the Prisma client can't know how to deep to go into the graph and what relationships to fetch. In fact, unless explicitly told otherwise (e.g. using the $fragment API), the client will never fetch any relationships and will always only fetch the scalar values. From the Prisma docs:
Whenever a model is queried using the Prisma client, all scalar fields of that model are fetched. This is true no matter if a single object or a list of objects is queried.
So, how to properly resolve this situation? In fact, the solution is not to make changes to the way how the Prisma client is used, but to implement an additional GraphQL resolver function!
The point about resolvers is that they're fetching the data for specific fields in your schema. In OP's case, there currently is no resolver that would "resolve" the user relation that's defined on the SystemUserLogin type:
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User! # GraphQL doesn't know how to resolve this
}
To resolve this situation, you need to implement a dedicated "type resolver" for it like so:
const resolvers = {
SystemUserLogin: {
user(parent, args, ctx) {
return ctx.db.systemUserLogin({id: parent.id}).user()
}
}
}
Full disclosure: I work at Prisma and we're working on adding better documentation and resources for that use case. Also check out this example where explicit resolvers for the author and posts relation fields are required for the same reason.
Hope that helps!
EDIT: We have also added a slightly more thorough explanation in the Prisma tutorial about Common resolver patterns.
Second parameter of prisma binding functions accept GraphQL query string. Changing following line from
const systemUser = await ctx.db.query.systemUserLogin({ username })
to
const systemUser = await ctx.db.query.systemUserLogin({ username }, `{id username user {id displayName}}`)
will give you the data of user.
Prisma binding will return only direct properties of model in case second parameter is not passed to it.

GraphQL mutation variables

I'm trying to do a simple mutation using GraphQL with the GraphiQL interface. My mutation looks like this:
mutation M($name: String) {
addGroup(name:$name) {
id,
name
}
}
with variables:
{
"name": "ben"
}
But it gives me the error: Variable $name of type "String" used in position expecting type "String!"
If I change my mutation to mutation M($name: String = "default") it works as expected. This looks like it's related to the type system, but I can't seem to figure out what the problem is.
You probably defined the input name as a non-null string (something like type: new GraphQLNonNull(GraphQLString) if using js server, or String! in plain GraphQL).
So your input in the mutation must match, which means it must also be a non-null string. If you change to the following, it should work:
mutation M($name: String!) {
addGroup(name:$name) {
id,
name
}
}
Also if you define a default value as you did, it will be a non-null string.
Finally, you could drop the requirement of being a non-null in the server.
I think in you addGroup() mutation the args for name is of type String! that is new GraphQLNonNull(GraphQLString) but in your mutation you specify as String which conflicts with the type system.
When you have an error like this check your database model, in my case I checked my schema and as mongo pluralizes the words was causing me error but I could fix it, also check the documentation.
mutation {
createProject(
name:"project two",
description:"project two"
) {
name
}
}
=> works

Resources