Apollo GraphQL multiple ID variables in one query - javascript

I am wondering if it is possible to pass multiple IDs to a useQuery for apollo hook. Or run a single query per ID, and if so how would I go about doing so.
I have the following query
const DECOR_SHEET = gql`
query GetDecorSheet($id: ID!) {
findDecorSheetByID(id:$id){
refIdArray
sheetName
_id
}
}
`;
and the following useQuery hook
const { loading, error, data: sheetData } = useQuery(DECOR_SHEET, {
variables: { id: id },
context: {
headers: {
authorization: cookieBearer,
},
},
});
I have the following IDs 293164663883956749, 293526016787218952 and I would like to return into one object in order to render into a component.
I am using Fauna DB and this is the input graphQL schema
type Catalog {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: User
}
type Decor {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: User!
visible: Boolean
}
type DecorSheet {
sheetName: String
refIdArray: String
owner: User!
}
type User {
email: String! #unique
catalog: Catalog
decor: [Decor!] #relation
decorSheet: [DecorSheet!] #relation
}
and this is the generated schema
directive #embedded on OBJECT
directive #collection(name: String!) on OBJECT
directive #index(name: String!) on FIELD_DEFINITION
directive #resolver(
name: String
paginated: Boolean! = false
) on FIELD_DEFINITION
directive #relation(name: String) on FIELD_DEFINITION
directive #unique(index: String) on FIELD_DEFINITION
type Catalog {
_id: ID!
decor: Boolean
clothing: Boolean
supplies: Boolean
owner: User
furniture: Boolean
_ts: Long!
}
input CatalogInput {
decor: Boolean
clothing: Boolean
supplies: Boolean
furniture: Boolean
owner: CatalogOwnerRelation
}
input CatalogOwnerRelation {
create: UserInput
connect: ID
disconnect: Boolean
}
scalar Date
type Decor {
purchaseDate: Date
visible: Boolean
image: String
description: String
_id: ID!
alterations: Boolean
cost: Int
pieces: Int
category: String
owner: User!
purchaser: String
itemNum: Int
_ts: Long!
}
input DecorInput {
description: String
pieces: Int
purchaser: String
alterations: Boolean
cost: Int
purchaseDate: Date
category: String
image: String
itemNum: Int
owner: DecorOwnerRelation
visible: Boolean
}
input DecorOwnerRelation {
create: UserInput
connect: ID
}
type DecorPage {
data: [Decor]!
after: String
before: String
}
type DecorSheet {
refIdArray: String
_id: ID!
sheetName: String
owner: User!
_ts: Long!
}
input DecorSheetInput {
sheetName: String
refIdArray: String
owner: DecorSheetOwnerRelation
}
input DecorSheetOwnerRelation {
create: UserInput
connect: ID
}
type DecorSheetPage {
data: [DecorSheet]!
after: String
before: String
}
scalar Long
type Mutation {
updateUser(
id: ID!
data: UserInput!
): User
createUser(data: UserInput!): User!
createDecorSheet(data: DecorSheetInput!): DecorSheet!
createDecor(data: DecorInput!): Decor!
deleteCatalog(id: ID!): Catalog
updateCatalog(
id: ID!
data: CatalogInput!
): Catalog
updateDecor(
id: ID!
data: DecorInput!
): Decor
updateDecorSheet(
id: ID!
data: DecorSheetInput!
): DecorSheet
deleteDecor(id: ID!): Decor
deleteUser(id: ID!): User
createCatalog(data: CatalogInput!): Catalog!
deleteDecorSheet(id: ID!): DecorSheet
}
type Query {
findUserByID(id: ID!): User
findCatalogByID(id: ID!): Catalog
findDecorByID(id: ID!): Decor
findDecorSheetByID(id: ID!): DecorSheet
}
scalar Time
type User {
catalog: Catalog
email: String!
_id: ID!
decor(
_size: Int
_cursor: String
): DecorPage!
decorSheet(
_size: Int
_cursor: String
): DecorSheetPage!
_ts: Long!
}
input UserCatalogRelation {
create: CatalogInput
connect: ID
disconnect: Boolean
}
input UserDecorRelation {
create: [DecorInput]
connect: [ID]
disconnect: [ID]
}
input UserDecorSheetRelation {
create: [DecorSheetInput]
connect: [ID]
disconnect: [ID]
}
input UserInput {
email: String!
catalog: UserCatalogRelation
decor: UserDecorRelation
decorSheet: UserDecorSheetRelation
}
There is an option to query with Fauna's FQL which may have a way of querying multiple IDs I will have to look into that, but would prefer to do this in graphQL with apollo if possible.
Thanks ahead of time

Thanks to support from FaunaDB
The following query and UDF does the trick
Query
type Query {
getMultipleDecors(DecorId: [ID!]): [Decor]
#resolver(name: "get_multiple_decors")
}
udf named get_multiple_decors
Query(
Lambda(
["input"],
Let(
{
data: Map(
Var("input"),
Lambda("x", Get(Ref(Collection("Decor"), Var("x"))))
)
},
Var("data")
)
)
)

If it's always exactly two ids, you can fetch both objects in a single query easily using field aliases:
const DECOR_SHEET = gql`
query GetDecorSheet($firstId: ID!, $secondId: ID!) {
firstDecorSheet: findDecorSheetByID(id: $firstId) {
refIdArray
sheetName
_id
}
secondDecorSheet: findDecorSheetByID(id: $secondId) {
refIdArray
sheetName
_id
}
}
`;

Related

GraphQL - "Field \"updateOwner\" of type \"Owner!\" must have a selection of subfields. Did you mean \"updateOwner { ... }\"?"

I'm trying to get Mutation Update query in GraphQL Playground. I'm basic level in GraphQL and in learning phase. I don't know how to create udpate Mutation for the below Owner code.
Any idea what I'm missing in my code / query?
---Resolver---
> #Mutation(() => Owner) updateOwner(
> #Args('id', { type: () => Int }) id: number,
> #Args('updateOwnerInput') updateOwnerInput: UpdateOwnerInput) {
> return this.ownersService.update(id, updateOwnerInput); }
---Service---
update(id: number, updateOwnerInput: UpdateOwnerInput) {
return this.ownersRepository.update(id, updateOwnerInput);
}
---dto---
#InputType()
export class UpdateOwnerInput extends PartialType(CreateOwnerInput) {
#Column()
#Field(() => Int)
id: number;
}
---entity---
#Entity()
#ObjectType()
export class Owner {
#PrimaryGeneratedColumn()
#Field(type => Int)
id: number;
#Column()
#Field()
name: string;
#OneToMany(() => Pet, pet => pet.owner)
#Field(type => [Pet], { nullable: true })
pets?: Pet[];
}
---schema---
type Pet {
id: Int!
name: String!
type: String
ownerId: Int!
owner: Owner!
}
type Owner {
id: Int!
name: String!
pets: [Pet!]
}
type Query {
getPet(id: Int!): Pet!
pets: [Pet!]!
owners: [Owner!]!
owner(id: Int!): Owner!
}
type Mutation {
createPet(createPetInput: CreatePetInput!): Pet!
createOwner(createOwnerInput: CreateOwnerInput!): Owner!
updateOwner(id: Int!, updateOwnerInput: UpdateOwnerInput!): Owner!
}
input CreatePetInput {
name: String!
type: String
ownerId: Int!
}
input CreateOwnerInput {
name: String!
}
input UpdateOwnerInput {
name: String
id: Int!
}
---GraphQL Query (I don't know whether it is correct or wrong)
mutation {
updateOwner (updateOwnerInput:{
id:6,
name: "josh",
})
}
---error---
"message": "Field \"updateOwner\" of type \"Owner!\" must have a selection of subfields. Did you mean \"updateOwner { ... }\"?",
You need to make a selection of subfields to return (even if you're not interested in any):
mutation {
updateOwner (updateOwnerInput:{
id:6,
name: "josh",
})
{
id
name
}
}
I tried the below code. Update Mutation working and able to update the field in Database column.
mutation updateOwner{
updateOwner(id:2, updateOwnerInput: {
id:2,
name: "test2"
})
{
id,
name
}
}

Typescript, check if request body has type User otherwise throw error

I'm stuck trying to solve a problem. I'm using express js to build a rest api. I want the user to be able to update their profile.
I've created a User model:
export type User = {
email: string
creation_date: number
first_name?: string
last_name?: string
payment_detals?: {
iban: string
last_updated: string
}
address?: {
city: string
street: string
house_number: string
postal_code: string
}
products?: string[]
}
But I want to receive the request body and update the value for that user in the database (No SQL, Firebase). But I don't want the user to add fields which are not specified in the User type.
How do I check if the request body has type User, if not throw an error?
The route:
const edit = async (req: Request, res: Response) => {
try {
let data = req.body
if (data instanceof User)
} catch (err) {
return res.status(501).json({ error: err.message })
}
return res.status(200).json({ status: 'ok' })
I can't find any help on the internet, so maybe someone could help me out?
So for example, if the payload of the post request is:
{
"name": "Jack"
}
It should throw an error, because name is not a member of User.
How can I solve this? All help is appreciated!
Updated now trying with classes:
export class CUser {
email: string
creation_date: number
first_name?: string
last_name?: string
payment_detals?: {
iban: string
last_updated: string
}
address?: {
city: string
street: string
house_number: string
postal_code: string
}
products?: string[]
}
The route
const edit = async (req: Request, res: Response) => {
let data = req.body
console.log(data instanceof CUser)
return res.status(200).json({ status: 'ok' })
}
When the request.body is:
{
"email": "mike#gmail.com",
"creation_date": 849349388935
}
The data instanceof CUser will always result to false. Wait is it maybe because data is an object?..
Types or interfaces that you define in Typescript are stripped when it's converted into Javascript, so you won't be able to able to check the type during runtime.
What you'll need to do is create a type-guard function that asserts true or false whether or not your request has those specific User properties.
For a good example see: How to check the object type on runtime in TypeScript?
You can create a constructor or function in a typescript class , which will take the req.body and only pick the required keys from the object, assign to this member variable and return you a new instance of the User object.
Now you can apply the checks on User instance or also can create a validateObject method inside the User class
I've solved this by writing a function which compares the request body with the types that I expect, and it works great! If there is something wrong with the fields or the required is wrong, the server will throw an error immediately. Here are some code snippets:
The functions
export type Schema = {
fields: { [key: string]: string }
required?: string[]
}
const required = (obj: any, required: string[]) => {
for (let key of required) {
if (obj[key] === undefined) return false
}
return true
}
export const validate = async (obj: any, model: Schema) => {
if (model.required) {
const status = required(obj, model.required)
if (!status) return false
}
for (let key of Object.keys(obj)) {
if (model.fields[key] === undefined) return false
else if (typeof obj[key] !== model.fields[key]) return false
}
return true
}
Example type
import { Schema } from './'
export type User = {
email: string
creation_date: number
subscription: string
first_name?: string
last_name?: string
payment_detals?: {
iban: string
last_updated: string
}
address?: {
city: string
street: string
house_number: string
postal_code: string
}
categories?: string[]
products?: {
ean: number
category?: string
notes?: string
}[]
}
export const UserSchema: Schema = {
fields: {
email: 'string',
subscription: 'string',
first_name: 'string',
last_name: 'string',
payment_details: 'object',
address: 'object',
categories: 'object',
products: 'object',
},
required: ['email']
}
On the server
let status = await validate(body, UserSchema)
if (!status) return res.status(422).json(Message.error.wrong_request_body)
// else continue

GraphQL: TypeError: Cannot read property of undefined

I have an Apollo GraphQL projects where I have created my own Query and Mutations. I have done using mock data and Query and Mutation works fine. But when I am trying to do with Sequelize ORM, I am getting the error
"TypeError: Cannot read property 'getListings' of undefined",
" at listings (/home/ayman/Desktop/apollo-graphql/graphql-app/functions/graphql.js:50:19)",
" at field.resolve (/home/ayman/Desktop/apollo-graphql/graphql-app/node_modules/graphql-extensions/dist/index.js:134:26)"
Query and Mutations in graphql.js:
const { ApolloServer, gql} = require("apollo-server-lambda");
const { Listing, User } = require("../db");
const jwt = require("jsonwebtoken");
const typeDefs = gql`
type Query {
listings: [Listing!]!
}
type Mutation {
createListing(input: CreateListingInput!): Listing!
}
input CreateListingInput {
title: String!
description: String
url: String!
notes: String
}
type Contact {
id: ID!
name: String!
company: Company
email: String
notes: String
}
type Company {
id: ID!
name: String!
logo: String
listings: [Listing!]!
url: String
}
type Listing {
id: ID!
title: String!
description: String
url: String!
notes: String
company: Company
contacts: [Contact!]!
}
`;
const resolvers = {
Query: {
listings(_, __, { user }) {
return user.getListings();
},
},
Mutation: {
createListing(_, { input }, { user }) {
return Listing.create({ ...input, userId: user.id });
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
exports.handler = server.createHandler();
I have Sequilize along with Postgres database:
db.js
const Sequelize = require("sequelize");
const sequelize = new Sequelize(process.env.DB_CONNECTION_STRING, {
dialect: "pg",
dialectModule: require('pg'),
dialectOptions: {
ssl: true,
},
});
class User extends Sequelize.Model {}
User.init(
{
email: Sequelize.STRING,
password: Sequelize.STRING,
},
{
sequelize,
modelName: "user",
}
);
class Listing extends Sequelize.Model {}
Listing.init(
{
title: Sequelize.STRING,
description: Sequelize.TEXT,
url: Sequelize.STRING,
notes: Sequelize.TEXT,
},
{
sequelize,
modelName: "listing",
}
);
Listing.belongsTo(User);
User.hasMany(Listing);
exports.sequelize = sequelize;
exports.User = User;
exports.Listing = Listing;
Github Link
Run using netlify dev
Go to URL: http://localhost:8888/.netlify/functions/graphql
Sample GraphQL query
{
listings {
id
title
description
url
company {
name
url
}
}
}
return user.getListings();
you probably mean User, because user is undefined
I see, you are trying to access user object from context. Please check the context definition. It should return an object containing user object explicitly.

Use array as variable for GraphQL Query

I want to query a nested field for multiple variables.
In this case, I want to query RPR, but only return RPR if the label of the nested Region is given. One variable for the nested Region (field: label) works fine, but how can I filter on multiple variables of the same field?
The way I see it, when I call up the query-client (in my case through Apollo), I want to give an array as a variable and let the query in the backend go through that array and return results based on any of the variables given in the array.
The resolver does nothing more then:
rPRs: (root, args, ctx, info) => {
return ctx.db.query.rPRs(args, info);
}
The relevant part of the schema:
type RPR {
id: ID! #Unique
RPRID: String! #Unique
state: String
region: Region!
resource: Resource!
price: Float
theme: String
editionTitle: String
information: String
}
type Region {
id: ID! #Unique
regionID: String! #Unique
label: String! #Unique
name: String! #Unique
aov: Float!
aov_multiplier: Float!
}
The current query to retrieve all 'RPR' with nested regions:
query ADVICE_RESOURCES_QUERY($theme: String, $regio: String) {
rPRs(where: {
theme: $theme,
region: {
label: $regio
}
})
{
RPRID
region {
label
}
}
}
you should be able to use the label_in filter and provide the array of Strings to it. Your where would then look like this:
where: {
theme: $theme,
region: {
label_in: $regio
}
}

Apollo Graphql Schema Stitching Conflict

I have a problem regarding GraphQL Schema stitching.
I have two Graphql Schemas:
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
}
type Query {
user(userId: String!): User
}
and
type User {
age: String!
}
type Query {
user(userId: String!): User
}
I now tried to merge the schemas using graphql-tools's mergeSchemas Function:
const schema = mergeSchemas({
schemas: [schema1, schema2]
});
But instead of what I'm trying to achieve (an extended User Type):
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
age: String!
}
type Query {
user(userId: String!): User
}
it resulted in this:
type Name {
firstname: String!
lastname: String!
}
type Address {
street: String!
number: Int!
}
type User {
name: Name!
address: Address!
}
type Query {
user(userId: String!): User
}
Only one of the UserTypes is displayed in the final schema.
I tried using the onTypeConflict API in mergeSchemas to extend the Type but I haven't made any results.
Is there a way to merge Schemas by extending Types on Conflict?
Here is a possible solution to merge the object types. Maybe it makes sense to filter by type name in onTypeConflict instead of merging every type.
import cloneDeep from 'lodash.clonedeep'
import { GraphQLObjectType } from 'graphql/type/definition'
import { mergeSchemas } from 'graphql-tools'
function mergeObjectTypes (leftType, rightType) {
if (!rightType) {
return leftType
}
if (leftType.constructor.name !== rightType.constructor.name) {
throw new TypeError(`Cannot merge with different base type. this: ${leftType.constructor.name}, other: ${rightType.constructor.name}.`)
}
const mergedType = cloneDeep(leftType)
mergedType.getFields() // Populate _fields
for (const [key, value] of Object.entries(rightType.getFields())) {
mergedType._fields[key] = value
}
if (leftType instanceof GraphQLObjectType) {
mergedType._interfaces = Array.from(new Set(leftType.getInterfaces().concat(rightType.getInterfaces())))
}
return mergedType
}
const schema = mergeSchemas({
schemas: [schema1, schema2],
onTypeConflict: (leftType, rightType) => {
if (leftType instanceof GraphQLObjectType) {
return mergeObjectTypes(leftType, rightType)
}
return leftType
}
})
Credits: The mergeObjectTypes function was written by Jared Wolinsky.
This should help
extend type User {
age: String!
}

Categories