Query data is received but can not be accessed from GraphQL API - javascript

I built the API with apollo server and everything works fine in graphiql. I make requests to the api from front-end react app with apollo client.
const [getUserPosts, { loading, error, data }] = useLazyQuery(GET_USER_POSTS);
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
SO getUserProfile fetches the user email from the express back end (I have an express serving react and a separate graphql api), then I query the posts of that user on the api. Below is the query itself
export const GET_USER_POSTS = gql`
query User($email: String) {
user(email: $email) {
email
posts {
content
}
}
}
`;
This is the typedefs and resolver on the api server
const typeDefs = gql`
type User {
email: String
posts: [Post]
}
type Post {
id: ID!
email: String
content: String
}
type Query {
users: [User]
posts: [Post]
user(email: String): [User]
post(id: String): [Post]
}
type Mutation {
addPost(email: String, content: String): [Post]
deletePost(id: String): [Post]
}
`;
const resolvers = {
Query: {
users: () => User.find(),
posts: () => Post.find(),
user: (parent, args) => User.find({ email: args.email }),
post: (parent, args) => Post.find({ _id: args.id }),
},
User: {
posts: async user => {
try {
const postsByUser = Post.find({ email: user.email });
return postsByUser;
} catch (error) {
console.log(err);
}
},
},
Mutation: {
addPost: async (parent, args) => {
const newPost = new Post({
email: args.email,
content: args.content,
});
try {
newPost.save();
} catch (err) {
console.log(err);
}
},
deletePost: async (parent, args) => {
try {
const deletedPost = await Post.deleteOne({ _id: args.id });
} catch (err) {
console.log(err);
}
},
},
};
then I try to console.log the data here
if (loading) {
console.log(loading);
}
if (error) {
console.log(error);
}
if (data) {
console.log(loading);
let test = data.user[0];
//I can see the data logged in the console as an object {email: "abc", posts: [array of posts]}
console.log(test);
}
BUT if I try to console.log(test.posts) react results with can not read property "posts" of undefined
UPDATE-1 !!
So when react results with the above error, I try to refresh the page again and it now can logs the "posts" array. But it sometimes take 2 or 3 refresh to make it work and sometimes when I refresh again it does not work anymore. Why is this happening ????
UPDATE-2 !!
So I try to troubleshoot with this line:
{data ? console.log(data.user[0].posts) : console.log("nothing")}
and interestingly it actually does log "nothing" a few times in the console before logging the data. But this is weird because I explicitly write that if only "data" is "true" then log it in the console. But somehow "data" is somtimes null itself. This data is provided by apollo client and it should be always true after loading is false, how is data still null after loading is false already ???

So I found the problem. Turns out it actually comes from within this block:
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
After observing in the network tab, it seems that my app try to send request to graphQL api before getUserProfile was done pulling user email, so it sent an empty request and hence received nothing. I was naive to think getUserProfile and getUserPosts will be executed synchronously. So I wrap getUserPosts with
if (userEmail) {
getUserPosts({ variables: { email: userEmail } });
}
So now only after I received the uerEmail then getUserPosts will be executed.

Related

failed login redirects me to /api/auth/error on next-auth

I'm using next-auth v. 4.18.8 in my login page. This is the final project of my Fullstack JS course. I'm using a newer version than the one used in the course (next-auth v. 3 is used)
When I insert the correct password, everything works as it should (it redirects me to the desired page).
Inserting the wrong password should throw me to /auth/signin?i=1 so I can handle this query.
However, it redirects me to http://127.0.0.1:3000/api/auth/error?error=Request%20failed%20with%20status%20code%20401
On console, it shows "POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)"
Here's my code:
Frontend: Login Page
const handleFormSubmit = async values => {
signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard'
})
}
Frontend: [...nextauth].js
export const authOptions = {
providers: [
CredentialsProvider({
name: 'credentials',
async authorize(credentials, req) {
const res = await axios.post('http://127.0.0.1:3000/api/auth/signin', credentials)
const user = res.data
if (user) {
return user
} else {
throw '/auth/signin?i=1'
}
}
})
],
session: {
jwt: true
},
jwt: {
secret: process.env.JWT_TOKEN
},
adapter: MongooseAdapter(process.env.MONGODB_URI)
}
export default NextAuth(authOptions)
Backend: signin.js controller
const authSignin = {
post: async (req, res) => {
const {
name,
email,
password,
} = req.body
await dbConnect()
const user = await UsersModel.findOne({ email })
if (!user) {
return res.status(401).json({ success: false, message: "invalid" })
}
const passIsCorrect = await compare(password, user.password)
if (passIsCorrect) {
return res.status(200).json({
_id: user._id,
name: user.name,
email: user.email
})
}
return res.status(401).json({ success: false, message: "invalid" })
}
}
export { authSignin }
Finally:
Backend: signin.js routes (using Next Connect):
import nextConnect from 'next-connect'
import { authSignin } from '../../../src/controllers/auth/signin'
const route = nextConnect()
route.post(authSignin.post)
export default route
One thing I noticed is that when inserting a wrong password, when the code reaches this line on controller:
return res.status(401).json({ success: false, message: "invalid" })
It wont continue to execute the [...nextauth].js file after axios.post, therefore not executing the code below, which should give me the 'i' query to handle on frontend (as stated in next-auth documentation):
if (user) {
return user
} else {
throw '/auth/signin?i=1'
}
The repository is on GitHub
I think if you pass redirect:false here
const handleFormSubmit = async values => {
signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard',
redirect: false,
})
}
Looks like when next-auth encounters an error, it automatically redirects. By setting the redirect option it will not automatically redirect so you could handle the error on client side like this
const handleFormSubmit = async values => {
const signInResult=signIn('credentials', {
email: values.email,
password: values.password,
callbackUrl: 'http://127.0.0.1:3000/user/dashboard',
redirect: false,
})
If (signInResult.error){
// Handle Error on client side
}
}
Also you should not make api request in authorize. it will delay the process. you could run the signin logic inside the authorize

I can't create a post in my db, the problem is that it returns the user id as null

My postman request :
{
"content": "my content",
"likes": 0
}
Response in postman :
{
null
}
Here is my code:
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Post extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
models.Post.belongsTo(models.User, {
foreignKey: {
allowNull: false,
name: "userId"
}
})
}
};
Post.init({
content: DataTypes.STRING,
comments: DataTypes.STRING,
attachment: DataTypes.STRING,
likes: DataTypes.INTEGER
}, {
sequelize,
modelName: 'Post',
});
return Post;
};
controllers :
module.exports.createPost = async (req, res, next) => {
const token = req.cookies.jwt;
const decoded = jwtAuth.verify(token, process.env.TOKEN_SECRET);
console.log(decoded);
const userIdFound = await User.findByPk(decoded.id);
I can't get the user id despite the request it returns a null value :
try {
const { body } = req;
console.log(body);
await User.findOne({ where: { id: userIdFound } })
.then((userFound) => {
//Create a Post if User Id is valid
if (userFound) {
const newPost = Post.create(
{ ...body, userId }
).then(() => res.status(201).json(newPost))
.catch(err => res.status(404).json('Unauthorizated request !'))
}})
Les erreurs :
} catch (err) {
throw new Error(err);
}
}
thank you very much for your help (I'm still a beginner, there will surely be some mistakes).
I don't understand from where you "pushing" the userIdFound on your where clause in the querie.
But I think you can try to insert the value id in a const, and use him on the where. Supposing the id field name in your HTML sending the post or get request is "id", you can insert it like this:
const userIdFound = req.body.id;
await User.findOne({ where: { id: userIdFound } })
//Some code for the logic
You can see more in the documentation here

GraphQl Mutation: addUser not creating user

I’m refactoring a Google Books app from a Restful API to GraphQL, and I am stuck on a mutation not behaving the way I expect.
When a user fills out the form found on Signup.js the Mutation ADD_USER should create a user within Mongoose, this user should have a JWT token assigned to them, and user should be logged in upon successful execution of the Mutation.
Actions observed:
• Mutation is being fired off from the front end. When I open developer tools in the browser I can see the Username, Email and Password being passed as variables.
• I have tried console logging the token, and keep getting an undefined return
• When I try to run the mutation in the GraphQL sandbox I get a null value returned.
• When I console log the args in resolvers.js no value appears on the console, which tells me the request is not reaching the resolver.
SignupForm.js (React FE Page)
import React, { useState } from "react";
import { Form, Button, Alert } from "react-bootstrap";
import { useMutation } from "#apollo/client";
import { ADD_USER } from "../utils/mutations";
import Auth from "../utils/auth";
const SignupForm = () => {
// set initial form state
const [userFormData, setUserFormData] = useState({
username: "",
email: "",
password: "",
});
// set state for form validation
const [validated] = useState(false);
// set state for alert
const [showAlert, setShowAlert] = useState(false);
const [addUser] = useMutation(ADD_USER);
const handleInputChange = (event) => {
const { name, value } = event.target;
setUserFormData({ ...userFormData, [name]: value });
};
const handleFormSubmit = async (event) => {
event.preventDefault();
// check if form has everything (as per react-bootstrap docs)
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
try {
///Add user is not returning data. payload is being passed as an object
const response = await addUser({
variables: { ...userFormData },
});
if (!response.ok) {
throw new Error("OH NO!SOMETHING WENT WRONG!");
}
const { token, user } = await response.json();
console.log(user);
Auth.login(token);
} catch (err) {
console.error(err);
setShowAlert(true);
}
setUserFormData({
username: "",
email: "",
password: "",
});
};
Mutation.js
export const ADD_USER = gql`
mutation addUser($username: String!, $email: String!, $password: String!) {
addUser(username: $username, email: $email, password: $password) {
token
user {
username
email
}
}
}
`;
typeDefs.js
const { gql } = require("apollo-server-express");
const typeDefs = gql`
input SavedBooks {
authors: [String]
description: String
bookId: String
image: String
link: String
title: String
}
type Books {
authors: [String]
description: String
bookId: ID
image: String
link: String
title: String
}
type User {
_id: ID
username: String
email: String
password: String
savedBooks: [Books]
}
type Auth {
token: ID!
user: User
}
type Query {
me: User
}
type Mutation {
##creates a user profile through the Auth type, that way we can pass a token upon creation
addUser(username: String!, email: String!, password: String!): Auth
login(email: String!, password: String!): Auth
saveBook(bookData: SavedBooks): User
deleteBook(bookId: ID!): User
}
`;
module.exports = typeDefs;
 
resolvers.js
const { User, Book } = require("../models");
const { AuthenticationError } = require("apollo-server-express");
const { signToken } = require("../utils/auth");
const resolvers = {
Query: {
me: async (parent, args, context) => {
if (context.user) {
return User.findOne({ _id: context.user._id }).populate("books");
}
throw new AuthenticationError("You need to log in");
},
},
};
Mutation: {
//try refactoring as a .then
addUser: async (parent, args) => {
//create user profile
await console.log("resolver test");
console.log(args);
const user = await User.create({ username, email, password });
//assign token to user
const token = signToken(user);
return { token, user };
};
login: async (parent, { email, password }) => {
const user = User.findOne({ email });
if (!user) {
throw new AuthenticationError("Invalid Login Credentials");
}
const correctPw = await profile.isCorrectPassword(password);
if (!correctPw) {
throw new AuthenticationError("Invalid Login Credentials");
}
const token = signToken(user);
return { token, user };
};
saveBook: async (parent, { bookData }, context) => {
if (context.user) {
return User.findOneAndUpdate(
{ _id: context.user._id },
{ $addToSet: { savedBooks: bookData } },
{ new: true }
);
}
throw new AuthenticationError("You need to log in");
};
deleteBook: async (parent, { bookId }, context) => {
if (context.user) {
return User.findOneAndUpdate(
{ _id: contex.user._id },
//remove selected books from the savedBooks Array
{ $pull: { savedBooks: context.bookId } },
{ new: true }
);
}
throw new AuthenticationError("You need to log in");
};
}
module.exports = resolvers;
auth.js
const jwt = require("jsonwebtoken");
// set token secret and expiration date
const secret = "mysecretsshhhhh";
const expiration = "2h";
module.exports = {
// function for our authenticated routes
authMiddleware: function ({ req }) {
// allows token to be sent via req.query or headers
let token = req.query.token || req.headers.authorization || req.body.token;
// ["Bearer", "<tokenvalue>"]
if (req.headers.authorization) {
token = token.split(" ").pop().trim();
}
if (!token) {
return req;
}
// verify token and get user data out of it
try {
const { data } = jwt.verify(token, secret, { maxAge: expiration });
req.user = data;
} catch {
console.log("Invalid token");
return res.status(400).json({ message: "invalid token!" });
}
// send to next endpoint
return req;
},
signToken: function ({ username, email, _id }) {
const payload = { username, email, _id };
return jwt.sign({ data: payload }, secret, { expiresIn: expiration });
},
};
Basically, I have combed from front to back end looking for where I introduced this bug, and am stuck. Any suggestions or feedback is greatly appreciated.
I was able to figure out the issue. First, a syntax error on resolver.js was preventing my mutations from being read.
Next, I made the following adjustment to handleFormSubmit on SignupForm.js
try {
///Add user is not returning data. payload is being passed as an object
const {data} = await addUser({
variables: { ...userFormData },
});
console.log(data)
console.log(userFormData)
**Auth.login(data.addUser.token);**
} catch (err) {
console.error(err);
setShowAlert(true);
}
That way my FE was properly accounting for what my Auth Middleware was passing back after successful user creation. Thanks for your help xadm, being able to talk this out got me thinking about where else to attack the bug.

Query resolver inside Mutation await function omitted

I'm having a little trouble when querying inside a mutation, I believe maybe I'm not calling correctly the query, because it executes but is not waiting for the response so I get an undefined value, please correct me.
Please note that I'm using prisma-binding
This is my mutation resolvers:
const Mutation = {
async signUp(parent, args, ctx, info) {
const password = await bcrypt.hash(args.password, 10)
const user = await ctx.db.mutation.createUser({ data: {...args, password} })
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},
async login(parent, args, ctx, info) {
const user = await ctx.db.query.users( {where:{email: args.email}}, info)
if (!user) {
throw new Error('No such user found')
}
const valid = bcrypt.compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},
};
module.exports = Mutation;
In the login function when querying the user or users I try both queries even knowing I have email as unique field it always print No such user found due I receive undefined, IDK if it's because prisma-binding or not doing correctly the function call to prisma.
Here is my schema.graphql
type Query {
// some queries
}
type Mutation {
signUp(
name: String!
email: String!
password: String!
): AuthPayLoad
login(
email: String!
password: String!
): AuthPayLoad
}
type AuthPayLoad {
token: String
user: User
}
So for prisma-binding I don't have to define the query users(), the binding will handle that right?? Even if was that, I would have another error which isn't the case.
Maybe I'm missing some little detail, will be grateful if someone point me to the right direction.
Thanks in advance...
You are passing the wrong selection set to the query. You are returning AuthPayload in login mutation but you are passing it to the user query which is surely incompatible.
try this
async login(parent, args, ctx, info) {
const user = await ctx.db.query.user( {where:{email: args.email}},`{ id name email }`) // pass in the other fields you want in this selection set
if (!user) {
throw new Error('No such user found')
}
const valid = bcrypt.compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},

How to skip a promise that doesn't work?

async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
})
);
const results = await axios.all(promises)
const KPIs = results.map(v => v.data.value);
}
What I am trying to do is:
get an axios call to fetch the name of the projects.
Use those fetched names to call multiple axios calls. This is supposed to give me some data from that project.
When I do the second axios call, there are some API calls that won't work because the data does not exist in that project. This was intended. But it will break by giving me the 404 error and won't reach the line const results = await axios.all(promises).
I want it to skip that whenever it doesn't exist and only call it from the one that exists and store the fetched data into KPIs. How can I do that?
EDIT
The first axios call returns an array of objects
ex)
{id: "id...", name: "Javelin", url: " .visualstudio.com/_apis/projects/6a93eab2-4996-4d02-8a14-767f02d94993", state: "wellFormed", revision: 99, …}
Something like that. So, I literally just get the .name of this object and put it into my url like:
axios.get({id}.extmgmt.visualstudio.com/_apis/ExtensionManagement/InstalledExtensions/{id}/..../${project.name}_test/Documents
This wraps the call in a promise that won't fail.
You also need to consider than you probably want to filter out null responses.
async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(fallible);
const results = await axios.all(promises)
const KPIs = results.filter(v => v).map(v => v.data.value);
}
function fallibleCall(project) {
return new Promise((resolve, reject) => {
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).then(resolve).catch(resolve);
})
}
If the problem is with fetching project data you just need to handle errors that occur there, one way is to simply catch the error and return null instead.
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
return null;
})
);
You can easily fix that by handling 404 responses by yourself and keep rejecting for all other errors as follows:
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
if (err.response.status === 404) {
return null; // or an empty array or whatever you want
}
throw err;
});
);
See https://github.com/axios/axios#handling-errors for details.

Categories

Resources