This question already has answers here:
Why does a GraphQL query return null?
(6 answers)
Closed 2 years ago.
I have been losing all Saturday trying to figure out this. I am good in frontend and decided to jump in and learn back end as well. I have been very interested in graphQl and since I was already doing a lot with firebase it was a go.
I decided to learn the graphQl server but cannot figure out what I am doing wrong.
I have this resolver to create a firebase account:
module.exports = {
Mutation: {
registerUser: async (
_,
{ registerInput: { username, email, password } },
context,
info
) => {
const register = await admin
.auth()
.createUser({
email,
emailVerified: false,
password: password,
displayName: username,
disabled: false
})
.then(function(userRecord) {
console.log("Successfully created new user:", userRecord.uid);
const {userData} = admin
.firestore()
.collection("users")
.doc(userRecord.uid)
.set(
{
email,
role: "user",
username,
uid: userRecord.uid
}, {merge: true});
return register
})
.catch(function(error) {
console.log("Error creating new user:", error);
});
return register
}
and in the schema:
type Mutation {
registerUser(registerInput: RegisterInput): User!
type User {
firstName: String
lastName: String
password: String!
adress: String
postCode: String
role: String
ort: String
email: String!
phone: String
responseRate: Float
responseTime: Int
profilePicture: String
token: String
username: String!
personnummer: Int
}
input RegisterInput{
username: String!
email: String!
password: String!
}
However when I run it in the playground, sometimes the account and the document about the user gets created, other times doesn't, but I always get the same error on the playground:
"errors": [
{
"message": "Cannot return null for non-nullable field Mutation.registerUser.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"registerUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field Mutation.registerUser.",
" at completeValue
Your resolver is an async function, so it will always return a Promise, but that promise will resolve to whatever is returned inside the function. If you return undefined or null, then the field in question (registerUser) will resolve to null. Since you've told GraphQL that can't happen, it throws an error to that effect.
When you call then on a Promise, whatever is returned inside then's callback becomes the value the Promise resolves to (provided no errors are thrown that would cause the Promise to reject). What you're returning inside the then callback in your code is register, which is effectively undefined because it's being used before being initialized.
If you're already using async/await syntax, there's no point in continuing to use then. Just stick with await -- it will keep your code cleaner, easier to read and less error-prone.
const userRecord = await admin
.auth()
.createUser({...})
// if `set` returns a Promise, we should await that as well
const { userData } = await admin
.firestore()
.collection("users")
.doc(userRecord.uid)
.set(...)
return userData
Note that GraphQL will catch any errors or rejected Promises for you and surface them as part of the response, so you don't need to use try/catch here unless you want to mask or otherwise format the error.
Related
I've created a simple createUser function which is executed on call. I have one problem though. The function is crashing when the user is trying to register with an already existing email. I mean, it's ok, since no one wants to have 2 users with the same email address but I want to prevent crushing function, instead, I want to send an error message as a response.
export const createUserTest = functions.https.onCall((data, context) => {
const {email, password} = data;
return new Promise((resolve, reject)=>{
try{
admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
resolve({
result: 'success',
user: user,
}) ;
})
.catch((error) => {
reject(error) ;
});
}catch(error) {
reject (error)
}
})
});
I tried to put the function in to try/catch block but it didn't help. Do you have an idea of how I can achieve my goal?
As explained in the doc for Callable Cloud Functions, "to ensure the client gets useful error details, return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
The error has a code attribute that can be one of the values listed here. In your case, the most appropriate seems to be already-exists.
On, the other hand, you'll find here the Admin SDK Authentication errors list and you'll see that in case the provided email is already in use by an existing user the error code is auth/email-already-exists.
So you can adapt your code as follows:
export const createUserTest = functions.https.onCall((data, context) => {
const { email, password } = data;
return admin
.auth()
.createUser({
email: email,
emailVerified: false,
password: password,
disabled: false,
})
.then((user) => {
return {
result: 'success',
user: user,
}
})
.catch((error) => {
if (error.code === 'auth/email-already-exists') {
throw new functions.https.HttpsError('already-exists', 'The provided email is already in use by an existing user');
} else {
throw new functions.https.HttpsError('...other code....', '...');
// If an error other than HttpsError is thrown, your client instead receives an error with the message INTERNAL and the code internal.
}
});
});
See here in the doc, how to handle errors on the client side. If error.code == 'already-exists' you know that it's because the email is already in use.
This question already has answers here:
Cloud Firestore: Enforcing Unique User Names
(8 answers)
Closed 3 years ago.
What i'am attempting to do is create new users with Firebase authentication and create their own document entry within Firestore. So far everything went well until i wanted to create unique usersnames. What is the most optimal way of going about doing this? should i create an array of all the users to cross-reference upon each sign up, or perhaps create a collection dedicated to Usernames with their email attached (which you can see i did in the second call to Firebase)
Firebase auth takes in email and passwords only so thats out of the question, or at least from what i gather.
export const signInUser = async ({ name, email, password }) => {
try {
await firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(cred => {
return firebase
.firestore()
.collection("users")
.doc(cred.user.uid)
.set({
name: name,
email: email,
friends: []
});
});
await firebase
.firestore()
.collection("usernames")
.doc(name)
.set({ username: name, email: email });
firebase.auth().currentUser.updateProfile({
displayName: name
});
return {};
} catch (error) {
switch (error.code) {
case "auth/email-already-in-use":
return {
error: "E-mail already in use."
};
case "auth/invalid-email":
return {
error: "Invalid e-mail address format."
};
case "auth/weak-password":
return {
error: "Password is too weak."
};
case "auth/too-many-requests":
return {
error: "Too many request. Try again in a minute."
};
default:
return {
error: "Check your internet connection."
};
}
}
};
I'd skip the "usernames" collection here, as you're basically creating an index, which fireStore already does for you. To check whether a name is unique, you can do:
const snapshot = await firestore.collection("users").where("name", "==", name).get();
// The name is taken if snapshot.empty is false.
When I set up my schema as following:
type Mutation {
createUser(data: CreateUserInput!): User!
}
type User {
id: ID!
name: String!
password: String!
email: String!
posts: [Post!]!
comments: [Comment!]!
}
and my resolver:
const Mutation = {
async createUser(parent, args, { prisma }, info) {
if(args.data.password.length < 8) {
throw new Error("Pasword must be 8 characters or longer")
}
return prisma.mutation.createUser({
data: {
...args.data,
password
}
})
}
}
how does GraphQL know that createUser is associated with my User model? I could set it up so that createUser returns token instead of User (after generating a token) or I could rename createUser to createPerson. I have never defined the association between createUser and User. I'm unsure how my data input through createUser gets directed to be saved in the user table, instead of another table.
There is no association between the two.
Your resolver could just as easily return a plain object with some dummy data:
async createUser(parent, args, { prisma }, info) {
...
return {
id: 1,
name: 'Kevvv',
password: 'password',
email: 'kevvv#stackoverflow.com',
}
}
or use some other means of fetching the user:
async createUser(parent, args, { prisma }, info) {
...
return fetchUserFromAPI()
// or some other data source like a DB
}
prisma.mutation.createUser returns a Promise that resolves to an object which represents the created user. This object happens to have properties that match the User type you specified in your schema, but otherwise there's nothing "special" about it.
I am using mongoose to get person data from database. This is the code i use:
return new Promise((resolve, reject) => {
Person.findOne({}, (err, result) => {
if(err) {
reject(err);
} else {
console.log(result);
console.log(result.firstname);
console.log(result.githubLink);
resolve(result);
}
});
});
This is output from console.log(result)
{ _id: 593c35e6ed9581db3ef85d75,
firstname: 'MyName',
lastname: 'MyLastName',
jobtitle: 'Web Developer',
email: 'foo#example.com',
githubLink: 'https://github.com/myGithub' }
And this is result from console.log(result.firstname); and console.log(result.githubLink);
MyName
undefined
Is this promise somehow messing up with this result? It's really weird because logging only the result shows my github link and logging the link says undefined.
If you have fields present in your database object that are not actually present in the Schema defined for the model, then they will still "log" but you cannot access the values of the property normally.
In most cases you really want to define the item properly in your schema:
githubLink: String
Or you can access properties you deliberately do not want to define using the .get() method:
result.get('githubLink')
try {
const user = await User.create({
username,
password: hash,
email,
});
return res.json({
status: 'success',
object: {
username: user.username,
email: user.email,
},
});
} catch (error2) {
return res.status(500).send('Username already in use');
}
I'm testing to make sure you can't add the same two usernames to the database, and you can't it throws an error, but the error isnt being caught and the client never gets any type of response from the server after it makes the request. does anyone know why this is happening?