What this 400 bad req is actually asking for ion my apollo client query? - graphql

I'm running this exact same query on apolloStudio and the response it's correct but in the code I'm getting a 400 bad req, I assume that the syntax is wrong, and it's quite complex given the fact that I'm querying an input inside a query. Here's an image of the response in apolloStudio:
Here's the code:
const { result, loading, error } = useQuery(
gql`
query GetUser(
$id: String
$ratingHistoryFilter: userRatingHistoryFilterInput
) {
getUser(_id: $id) {
_id
ratingHistory(ratingHistoryFilter: $ratingHistoryFilter) {
rating
date
}
}
}
`,
{
id: props.uid,
ratingHistoryFilter: {
dateFrom: '1994-06-24',
verified: 'false',
dateTo: '2022-07-06',
userID: {
condition: 'EQUAL',
value: '62c2d4100892074470ca374e'
}
}
}
)
console.log(result)
const userData = computed(() => result.value?.getUser ?? {})
console.log(userData.value)
Here's the console error:
This is the network response:
{"errors":[{"message":"Variable "$id" of type "String" used in position expecting type "String!".","code":"INTERNAL_SERVER_ERROR"},{"message":"Variable "$ratingHistoryFilter" of type "userRatingHistoryFilterInput" used in position expecting type "userRatingHistoryFilterInput!".","code":"INTERNAL_SERVER_ERROR"}]}

Related

How do I see the name of a logged in user?

I'm trying to access the current logged in user's name, and get it working in the Playground right now for apollo graphql. I'm using Prisma and I'm a beginner.
This is my schema.graphql:
type Query {
info: String!
userlist: [User!]!
me: User
}
type Mutation {
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
}
type AuthPayload {
token: String
user: User
}
type User {
id: ID!
name: String!
email: String!
}
Here is my src/resolvers/Query.js:
const { getUserId } = require("../utils");
function userlist(parent, args, context) {
return context.prisma.user.findMany();
}
function me(parent, args, context) {
const id = getUserId(context);
return context.prisma.user({ id });
}
module.exports = {
userlist,
me,
};
How do I test this in the playground? If I log in a user with this query:
mutation {
login(email: "alice#prisma.io", password: "graphql") {
token
user {
email
}
}
}
Then when I try to display "me" I get this:
query {
me {
id
name
}
}
Result:
{
"errors": [
{
"message": "Cannot read properties of undefined (reading 'authorization')",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"me"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read properties of undefined (reading 'authorization')",
" at getUserId (/Users/username/example-3/server/src/utils.js:10:36)",
" at me (/Users/username/example-3/server/src/resolvers/Query.js:8:14)",
// i deleted a bunch of stuff here, let me know if you would like to see it and I'll add it back
]
}
}
}
],
"data": {
"me": null
}
}
This is the utils.js:
const jwt = require("jsonwebtoken");
const APP_SECRET = "GraphQL-is-aw3some";
function getTokenPayload(token) {
return jwt.verify(token, APP_SECRET);
}
function getUserId(req, authToken) {
if (req) {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.replace("Bearer ", "");
if (!token) {
throw new Error("No token found");
}
const { userId } = getTokenPayload(token);
return userId;
}
} else if (authToken) {
const { userId } = getTokenPayload(authToken);
return userId;
}
throw new Error("Not authenticated");
}
module.exports = {
APP_SECRET,
getUserId,
};
Do I need to pass in the auth token somehow? Any idea why getUserId isn't working? I think I have something wrong, because the code I was using from an example has getUserId(req), but another solution I saw has getUserId(context), so is that what is wrong here? How do I change my Query to use req instead of context? I'm not understanding them enough to make this change.
You answered your own question:
Do I need to pass in the auth token somehow?
Yes. You're getting an error because you're not passing any headers from the playground so req.headers is undefined.
At the bottom left of the playground there's an area where you can define query variables and headers. Define one called "authorization" and put a valid token in the contents.
You should have a login method somewhere that creates a valid token, use that.

throw a descriptive error with graphql and apollo

Consider the following class:
// entity/Account.ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'
import { Field, Int, ObjectType } from 'type-graphql'
#ObjectType()
#Entity()
export class Account extends BaseEntity {
#Field(() => Int)
#PrimaryGeneratedColumn()
id: number
#Field()
#Column({ length: 50, unique: true })
#Index({ unique: true })
accountIdentifier: string
#Field({ nullable: true })
#Column({ length: 100 })
name?: string
}
With it's corresponding resolver:
// AccountResolver.ts
#Resolver()
export class AccountResolver {
#Mutation(() => Account)
async addAccount(#Arg('options', () => AccountInput) options: AccountInput) {
try {
// if (!options.accountIdentifier) {
// throw new Error(`Failed adding account: the accountIdentifier is missing`)
// }
return await Account.create(options).save()
} catch (error) {
if (error.message.includes('Cannot insert duplicate key')) {
throw new Error(
`Failed adding account: the account already exists. ${error}`
)
} else {
throw new Error(`Failed adding account: ${error}`)
}
}
}
}
Jest test file
// AccountResolver.test.ts
describe('the addAccount Mutation', () => {
it('should throw an error when the accountIdentifier is missing', async () => {
await expect(
client.mutate({
mutation: gql`
mutation {
addAccount(
options: {
name: "James Bond"
userName: "James.Bond#contoso.com"
}
) {
accountIdentifier
}
}
`,
})
).rejects.toThrowError('the accountIdentifier is missing')
})
The field accountIdentifier is mandatory and should throw a descriptive error message when it's missing in the request. However, the error thrown is:
"Network error: Response not successful: Received status code 400"
What is the correct way to modify the error message? I looked at type-graphql with the class-validators and made sure that validate: true is set but it doesn't give a descriptive error.
EDIT
After checking the graphql playground, it does show the correct error message by default. The only question remaining is how write the jest test so it can read this message:
{
"error": {
"errors": [
{
"message": "Field AccountInput.accountIdentifier of required type String! was not provided.",
Thank you for any help you could give me.
The ApolloError returned by your client wraps both the errors returned in the response and any network errors encountered while executing the request. The former is accessible under the graphQLErrors property, the latter under the networkError property. Instea dof using toThrowError, you should use toMatchObject instead:
const expectedError = {
graphQLErrors: [{ message: 'the accountIdentifier is missing' }]
}
await expect(client.mutate(...)).rejects.toMatchObject(expectedError)
However, I would suggest avoiding using Apollo Client for testing. Instead, you can execute operations directly against your schema.
import { buildSchema } from 'type-graphql'
import { graphql } from 'graphql'
const schema = await buildSchema({
resolvers: [...],
})
const query = '{ someField }'
const context = {}
const variables = {}
const { data, errors } = await graphql(schema, query, {}, context, variables)

Parameterized queries qraphQL

I want to make parameterized requests from the Apollo client to the Apollo server.
On client:
const GET_VALUES = gql`
query Values($desc: String!) {
Values
}
`;
function ValueSelector({ pickValue, dirDesc }) {
const { loading, data, error } = useQuery(GET_VALUES, {
variables: { dirDesc },
});
}
On server (schema):
type Query {
Values(desc: String!): [String]
#cypher(
statement: "MATCH (:Dir{description:$desc})-[:value]->(v) RETURN collect(v.TXTLG)"
)
}
Result:
[GraphQL error]: Message: Field "Values" argument "desc" of type "String!" is required, but it was not provided., Location: [object Object], Path: undefined
[Network error]: ServerError: Response not successful: Received status code 400
You should use desc instead of dirDesc in variables param of useQuery. Try this:
const { loading, data, error } = useQuery(GET_VALUES, {
variables: { desc: dirDesc },
});
const GET_VALUES = gql`
query Values($desc: String!) {
Values(desc: $desc)
}
`;
function ValueSelector({ pickValue, dirDesc }) {
const { loading, data, error } = useQuery(GET_VALUES, {
variables: { desc:dirDesc},
});
Your query declaration on the client is missing the actual variable. It should be something like this
const GET_VALUES = gql`
query Values($desc: String!) {
Values(dirDesc: $desc)
}
`;
Then you can use useQuery passing dirDesc.

Graphql returning Cannot return null for non-nullable field Query.getDate. As I am new to graphql I want to know is my approach is wrong or my code?

I have created resolver, schema and handler which will fetch some record from dynamoDB. Now when I perform query then I am getting "Cannot return null for non-nullable field Query.getDate" error. I would like to know whether my approach is wrong or there is any change required in code.
My code : https://gist.github.com/vivek-chavan/95e7450ff73c8382a48fb5e6a5b96025
Input to lambda :
{
"query": "query getDate {\r\n getDate(id: \"0f92fa40-8036-11e8-b106-952d7c9eb822#eu-west-1:ba1c96e7-92ff-4d63-879a-93d5e397b18a\") {\r\n id\r\n transaction_date\r\n }\r\n }"
}
Response :
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.getDate.",
"locations": [
{
"line": 2,
"column": 7
}
],
"path": [
"getDate"
]
}
],
"data": null
}
Logs of lambda function :
[ { Error: Cannot return null for non-nullable field Query.getDate.
at completeValue (/var/task/node_modules/graphql/execution/execute.js:568:13)
at completeValueCatchingError (/var/task/node_modules/graphql/execution/execute.js:503:19)
at resolveField (/var/task/node_modules/graphql/execution/execute.js:447:10)
at executeFields (/var/task/node_modules/graphql/execution/execute.js:293:18)
at executeOperation (/var/task/node_modules/graphql/execution/execute.js:237:122)
at executeImpl (/var/task/node_modules/graphql/execution/execute.js:85:14)
at execute (/var/task/node_modules/graphql/execution/execute.js:62:229)
at graphqlImpl (/var/task/node_modules/graphql/graphql.js:86:31)
at /var/task/node_modules/graphql/graphql.js:32:223
at graphql (/var/task/node_modules/graphql/graphql.js:30:10)
message: 'Cannot return null for non-nullable field Query.getDate.',
locations: [Object],
path: [Object] } ],
data: null }
2019-02-25T10:07:16.340Z 9f75d1ea-2659-490b-ba59-5289a5d18d73 { Item:
{ model: 'g5',
transaction_date: '2018-07-05T09:30:31.391Z',
id: '0f92fa40-8036-11e8-b106-952d7c9eb822#eu-west-1:ba1c96e7-92ff-4d63-879a-93d5e397b18a',
make: 'moto' } }
Thanks in advance!
This is your code:
const data = {
getDate(args) {
var params = {
TableName: 'delete_this',
Key: {
"id": args.id
}
};
client.get(params, function(err,data){
if(err){
console.log('error occured '+err)
}else{
console.log(data)
}
});
},
};
const resolvers = {
Query: {
getDate: (root, args) => data.getDate(args),
},
};
You're seeing that error because getDate is a a Non-Null field in your schema, but it is resolving to null. Your resolver needs to return either a value of the appropriate type, or a Promise that will resolve to that value. If you change data like this
const data = {
getDate(args) {
return {
id: 'someString',
transaction_date: 'someString',
}
}
}
you'll see the error go away. Of course, your goal is to return data from your database, so we need to add that code back in. However, your existing code utilizes a callback. Anything you do inside the callback is irrelevant because it's ran after your resolver function returns. So we need to use a Promise instead.
While you can wrap a callback with Promise, that shouldn't be necessary with aws-sdk since newer versions support Promises. Something like this should be sufficient:
const data = {
getDate(args) {
const params = //...
// must return the resulting Promise here
return client.get(params).promise().then(result => {
return {
// id and transaction_date based on result
}
})
}
}
Or using async/await syntax:
const data = {
async getDate(args) {
const params = //...
const result = await client.get(params).promise()
return {
// id and transaction_date based on result
}
}
}

GraphQL how to mutate data

I have a basic schema for mutating some data which looks like
const schema = new graphql.GraphQLSchema({
mutation: new graphql.GraphQLObjectType({
name: 'Remove',
fields: {
removeUser: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve(_, args) {
const removedData = data[args.id];
delete data[args.id];
return removedData;
},
},
},
})
});
Looking around google I cant find a clear example of the example query which needs to be sent to mutate.
I have tried
POST -
localhost:3000/graphql?query={removeUser(id:"1"){id, name}}
This fails with error:
{
"errors": [
{
"message": "Cannot query field \"removeUser\" on type \"Query\".",
"locations": [
{
"line": 1,
"column": 2
}
]
}
]
}
In order to post requests from the front-end application it is recommended to use apollo-client package. Say i wanted to validate a user login information:
import gql from 'graphql-tag';
import ApolloClient, {createNetworkInterface} from 'apollo-client';
client = new ApolloClient({
networkInterface: createNetworkInterface('http://localhost:3000/graphql')
});
remove(){
client.mutate({
mutation: gql`
mutation remove(
$id: String!
) {
removeUser(
id: $id
){
id,
name
}
}
`,
variables: {
id: "1"
}
}).then((graphQLResult)=> {
const { errors, data } = graphQLResult;
if(!errors && data){
console.log('removed successfully ' + data.id + ' ' + data.name);
}else{
console.log('failed to remove');
}
})
}
More information about apollo-client can be found here
Have you tried using graphiql to query and mutate your schema?
If you'd like to create a POST request manually you might wanna try to struct it in the right form:
?query=mutation{removeUser(id:"1"){id, name}}
(Haven't tried POSTing myself, let me know if you succeeded, i structured this out of the url when using graphiql)
You have to explicitly label your mutation as such, i.e.
mutation {
removeUser(id: "1"){
id,
name
}
}
In GraphQL, if you leave out the mutation keyword, it's just a shorthand for sending a query, i.e. the execution engine will interpret it as
query {
removeUser(id: "1"){
id,
name
}
}
cf. Section 2.3 of the GraphQL Specification
const client = require("../common/gqlClient")();
const {
createContestParticipants,
} = require("../common/queriesAndMutations");
const gql = require("graphql-tag");
const createPartpantGql = async (predictObj) => {
try {
let resp = await client.mutate({
mutation: gql(createContestParticipants),
variables: {
input: {
...predictObj,
},
},
});
let contestParticipantResp = resp.data.createContestParticipants;
return {
success: true,
data: contestParticipantResp,
};
} catch (err) {
console.log(err.message)
console.error(`Error creating the contest`);
return {
success: false,
message: JSON.stringify(err.message),
};
}
};

Resources