web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens une erreur "Impossible de retourner null pour un champ non nullable" lors d'une mutation?

J'essaie (Apollo) GraphQL côté serveur et j'ai eu un problème probablement stupide. J'essaie d'inscrire un utilisateur, mais je reçois toujours l'erreur indiquée dans l'image liée ci-dessous. Quel est le problème? Ignorez le flux d'authentification très simple, car je ne fais que tester le GraphQl

enter image description here

Voici les extraits de code pertinents:

Schéma

export default `

type User {
    id: ID!
    name: String!
    email: String!
}

type Query {
    allUsers: [User]
  currentUser: User
}

type Mutation {
    createAccount(name: String!, email: String!, password: String!): User
    loginUser(email: String!, password: String!): User
    updatePassword(email: String!, password: String!, newPassword: String!): User
    deleteAccount(email: String!, password: String!): User
}

`

Résolveurs

createAccount: async (
  parent,
  { name, email, password },
  { User },
  info
) => {
  try {
    // Check for invalid (undefined) credentials
    if (!name || !email || !password) {
      return 'Please provide valid credentials';
    }

    // Check if there is a user with the same email
    const foundUser = await User.findOne({ email });

    if (foundUser) {
      return 'Email is already in use';
    }

    // If no user with email create a new user
    const hashedPassword = await bcrypt.hash(password, 10);
    await User.insert({ name, email, password: hashedPassword });

    const savedUser = await User.findOne({ email });

    return savedUser;
  } catch (error) {
    return error.message;
  }
},
6
Collins Orlando

Le plus gros problème avec votre résolveur est que dans n'importe quel nombre de scénarios, au lieu de renvoyer un objet User, vous retournez une chaîne. Votre schéma spécifie que createAccount peut renvoyer un User ou null (si c'était User!, il ne serait pas nullable, puis null ne serait pas non plus un type valide).

Lorsque vous renvoyez une chaîne dans votre résolveur, car il attend un objet, il le contraint en un, puis commence à rechercher les propriétés User sur cet objet (comme name et email ). Ces propriétés n'existent pas, et comme il s'agit de propriétés non nulles sur votre objet User, renvoyer null/undefined pour elles entraîne une erreur.

Votre résolveur devrait probablement lancer toutes les erreurs qu'il doit lancer. Ensuite, ils seront renvoyés dans le cadre du tableau errors dans votre réponse. Par exemple:

// Check if there is a user with the same email
const foundUser = await User.findOne({ email })

if (foundUser) throw new Error('Email is already in use')

// If no user with email create a new user
const hashedPassword = await bcrypt.hash(password, 10);
await User.insert({ name, email, password: hashedPassword });

const savedUser = await User.findOne({ email });

return savedUser;

Maintenant, si vous avez un e-mail en double, la réponse ressemblera à ceci:

{
  "data": {
    "createAccount": null
  },
  "errors": [
    {
      "message": "Email is already in use",
      "locations": [
        {
          "line": 4,
          "column": 3
        }
      ],
      "path": [
        "createAccount"
      ]
    }
  ]
}

Si vous souhaitez manipuler la façon dont vos erreurs sont affichées sur le client, vous devez utiliser les options de configuration formatError ou formatResponse pour votre middleware de serveur Apollo. Il est également recommandé d'utiliser des erreurs personnalisées, ce qui vous permet d'ajouter des propriétés personnalisées comme code pour identifier plus facilement le type d'erreur du côté client.

Enfin, il n'est pas nécessaire de vérifier si le nom, l'e-mail ou le mot de passe sont définis dans votre résolveur - votre schéma a déjà ces entrées marquées comme non nulles, ce qui signifie que GraphQL renverra automatiquement une erreur si l'une d'entre elles est manquante.

11
Daniel Rearden