GraphQL nested query definition - javascript

I'm trying to create tree-like structure for my queries, to get rid off queries like
peopleList, peopleSingle, peopleEdit, peopleAdd, peopleDelete companyList, companySingle, companyEdit, companyAdd, companyDelete etc.
In the end I would like to send query like this:
query test {
people {
list {
id
name
}
single(id: 123) {
id
name
}
}
company {
list {
id
name
}
single(id: 456) {
id
name
}
}
}
mutation test2 {
people {
create(data: $var) {
id
name
}
}
people {
edit(id: 123, data: $var) {
id
name
}
}
}
This is part of my query object on people module:
people: {
type: //What type this should be?
name: 'Root of People queries',
fields: () => ({
list: {
type: peopleType,
description: 'Returns all people in DB.',
resolve: () => {
// resolve method implementation
}
},
single: {
type: peopleType,
description: 'Single row from people table. Requires ID argument.',
args: {
id: { type: new GraphQLNonNull(GraphQLID) }
},
resolve: () => {
// resolve method implementation
}
}
})
}
I have tried to put this snippet into GraphQLObjectType and then combine them together in RootQuery (using GraphQLObjectType again) - didn't work.
Alternative method could be to create new Type - like peopleQueriesType, inside this type specify all my queries as fields and then create single query for this object. But this seems odd to me - polluting my code with unnecessary objects just to merge my queries in tree-like shape.
I have tried to look at Apollo server implementation, if it can do this kind of query structure, but couldn't find any help in documentation.
I'm using node.js + express + graphql-js on my server.

Short answer:
type should be a GraphQLObjectType containing all the fields like this:
type: new GraphQLObjectType({ name: 'patientQuery', fields: { find, findOne } })
Details: I ended up with this query using the code below:
{
patient {
find {
id
active
}
findOne(id: "pat3") {
id
active
}
}
}
in patient/queries/index.js I have this
import findOne from './find-one.js';
import find from './find.js';
import { GraphQLObjectType } from 'graphql';
export default {
patient: {
type: new GraphQLObjectType({ name: 'patientQuery', fields: { find, findOne } }),
resolve(root, params, context, ast) {
return true;
}
}
};
then in queries.js
import patient from './patient/queries/index.js';
export default {
...patient
};
and finally my schema schema.js that is passed to graphql express server
import {
GraphQLObjectType,
GraphQLSchema
} from 'graphql';
import queries from './queries';
import mutations from './mutations';
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: queries
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: mutations
})
});

Related

How do I transform a MongoDB document into a NestJS DTO?

I have a data layer that reads and writes to a MongoDB instance. I only want to deal with MongoDB documents at that layer and not expose that implementation to my services.
Right now I am doing something like:
// users.repository.ts
...
async getUserById(id: string): Promise<UserDto> {
const user = await this.model.findOne({ _id: id }).exec();
return this.transformToDto(user);
}
private transformToDto(user: UserDocument): UserDto {
return {
id: user._id,
...etc
}
}
...
This seems overly verbose and there must be a simpler way to achieve this without adding a helper to every repository.
Is there a cleaner way to achieve this?
You can use class-transformer for that and you don't need to use extra helper methods it can be returned instantly.
import { plainToClass } from 'class-transformer';
class UserDto {
id: string;
email: string;
role: string;
}
class Service {
async getUserById(id: string): Promise<UserDto> {
const user = await this.model.findOne({ _id: id }).exec();
return plainToClass(UserDto, user);
}
}
It will return transformed value which is UserDto
UserDto { id: 'U-111', email: 'U-111#email', role: 'user' }

Cannot query across one-to-many for property NestJS and TypeORM

I have two entities one is car and another one is carAvailability
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { CarAvailability } from 'src/car-availabilitys/car-availability.entity';
#Entity('cars')
export class Car {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => CarAvailability, (carAvailability) => carAvailability.car, {
eager: true,
cascade: true,
})
availabilities: CarAvailability[];
}
I am trying to add a service that queries and filters cars based on the availabilities. In My Service and tried two ways:
Method 1 with repo functions:
async test () {
const startDateTime = '2012-04-24 02:25:43.511';
return await this.repo.find({
relations: ['availabilities'],
where: {
availabilities: {
start_date_time: startDateTime
}
}
});
}
Method 2 with query builder:
async test () {
const startDateTime = '2012-04-24 02:25:43.511';
return this.repo.createQueryBuilder('cars')
.innerJoin('cars.availabilities', 'car_availabilities')
.where("cars.availabilities.start_date_time = :startDateTime", { startDateTime })
.getMany();
}
Method 1 error:
Error: Cannot query across one-to-many for property availabilities
Method 2 error:
QueryFailedError: missing FROM-clause entry for table "availabilities"
I feel like I am missing something but I am not sure. Have referred both NestJS and TypeORM docs but can't seem to figure out what went wrong.
Using Method 2, because you aliased car.availabilities as car_availabilities, your where clause should use the alias name:
car_availabilities.start_date_time
not:
cars.availabilities.start_date_time
Using Method 1 repo functions with nested query builder:
async test () {
const startDateTime = '2012-04-24 02:25:43.511';
return await this.repo.find({
relations: ['availabilities'],
where: (qb) => {
qb.where('availabilities.start_date_time = :startDateTime', {
startDateTime
});
}
});
}
Remove cascade from oneToMany side and put:
{
cascade: true,
onDelete: 'CASCADE',
onUpdate:'CASCADE'
}
on manyToOne side.
And if you going to use relations: ['availabilities'] you can remove eager: true.

Context not being passed to nested Apollo GraphQL resolver

I am using Apollo Server v2 for my project
I have added auth like given here https://www.apollographql.com/docs/apollo-server/features/authentication/
I wanted to include nested resolver in my Query and Mutation so I did as per https://stackoverflow.com/a/40916089/7584077
The thing is my resolver is a lil bit more complex than the one shown above
// typeDefs/typeDefs.js
import { gql } from "apollo-server-express";
import issueTracker from "./issueTracker";
const base = gql`
scalar Timestamp
type Query {
ping: String!
}
type Mutation {
ping: String!
}
`;
export default [base, issueTracker];
// typeDefs/issuetracker.js
import { gql } from "apollo-server-express";
export default gql`
type Comment {
id: ID
message: String
commentBy: User
createdAt: Timestamp
updatedAt: Timestamp
version: Int
}
type Issue {
id: ID
requestId: ID
title: String
issueNumber: Int
status: Int
tags: [String]
assignees: [User]
createdBy: User
comments: [Comment]
createdAt: Timestamp
updatedAt: Timestamp
version: Int
}
input CreateIssueRequest {
requestId: ID!
title: String!
createdBy: ID!
message: String!
assignees: [ID]!
}
type IssueTrackerQuery {
ping: String!
}
type IssueTrackerMutation {
createIssue(request: CreateIssueRequest!): Issue
}
extend type Query {
IssueTracker: IssueTrackerQuery
}
extend type Mutation {
IssueTracker: IssueTrackerMutation
}
`;
And a lil modified version of the stackoverflow answer above.
Here is my combined resolver.
// resolvers/resolvers.js
import IssueTracker from "./issueTracker";
export default {
Query: {
ping: () => "ping!",
IssueTracker: () => ({
ping: IssueTracker.ping,
}),
},
Mutation: {
ping: () => "ping!",
IssueTracker: () => ({
createIssue: IssueTracker.createIssue,
}),
},
};
This is because I wanted Query & Mutation to be completely separate.
Here is my IssueTracker resolver
// resolvers/issueTracker.js
export default {
ping: () => "ping",
createIssue: async (parent, args, context) => {
console.log(parent);
console.log(args);
console.log(context);
// create issue as per request and return
}
The thing is here is that, parent actually is the args field!
And I need userId from context to make sensible data.
Hmm, the SDL first approach can be a bit tricky. It's not easy to explain what is wrong here but I will do my best. First let me tell you what to need to do to make this work and then I will explain what goes wrong.
Create a IssueTrackerMutation field in the resolver map:
export default {
Query: {
ping: () => "ping!",
IssueTracker: () => ({ // same here but I will just do the mutation for you
ping: IssueTracker.ping,
}),
},
Mutation: {
ping: () => "ping!",
IssueTracker: () => null, // or whatever you want as a root here
},
IssueTrackerMutation: {
createIssue: IssueTracker.createIssue
}
};
Note the difference between creating a "pure" resolver for the IssueTracker and returning an object for IssueTracker with a createIssue method.
Now the function should be called with the expected parameters. The reason why the parent argument seems to be missing is the very special implementation of the default resolver. The resolver is intended to work with an object oriented style where fields can be fields or methods. You can imagine the resolver to work like this:
defaultResolver(fieldName, parent, args, context, info) {
if (typeof parent !== 'object') {
throw "Need object to default resolve";
}
if (typeof parent[fieldName] === 'function') {
return parent[fieldName](args, context, info);
}
return parent[fieldName];
}
This would allow you to write your data access objects in the following manner:
class IssueTracker {
issues = []
async createIssue(args, ctx, info) {
const issue = await createIssue(args.request);
this.issues.push(issue);
return issue;
}
}
const issueTracker = new IssueTracker();
export default {
// ...
Mutation: {
// ...
IssueTracker: () => issueTracker,
},
IssueTrackerMutation: {
createIssue: IssueTracker.createIssue
}
};
This is not talked about that much but is probably relatively close how GraphQL at Facebook works. The community seems to move more logic into the resolvers.

Set resolver on individual field with GraphQL

I would like to set a resolver, on an individual field that returns a string.
For this example. I want to take the title attribute, and make it .toUpperCase
Schema
type Product {
title(uppercase:Boolean!): String!
}
type Query {
products: [Product]
}
Resolver
Query: {
products: () => [{title:'foo'}],
products.title: (stringToRtn, { action }) => {
return action ? stringToRtn.toUpperCase : stringToRtn
}
}
Here is the solution:
const resolvers = {
Product: {
title: product => {
return product.title.toUpperCase();
}
},
Query: {
products: () => [{title:'foo'}]
}
};
If you're using TypeScript, use these typeDefs:
type Product {
title: String!
}
type Query {
products: [Product]
}
Another way is to use custom directive like "#upperCase", but it's too complex for this.
TypeScript update directive way
Remove : GraphQLField<any, any> if you're not using TypeScript.
#uppercase directive implementation:
import { SchemaDirectiveVisitor } from 'graphql-tools';
import { GraphQLField, defaultFieldResolver } from 'graphql';
class UppercaseDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField<any, any>) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function resolver(...args) {
const result = resolve.apply(this, args);
if (typeof result === 'string') {
return result.toUpperCase();
}
return result;
};
}
}
export { UppercaseDirective };
If you're using TypeScript use these typeDefs:
const typeDefs: string = `
enum Status {
SOLD_OUT
NO_STOCK
OUT_OF_DATE #deprecated(reason: "This value is deprecated")
}
type Book {
id: ID!
title: String #uppercase
author: String
status: Status
name: String #deprecated(reason: "Use title instead")
}
type Query {
books: [Book]!
bookByStatus(status: Status!): [Book]!
}
`;
schema:
(Remove : GraphQLSchema if you're not using TypeScript.)
const schema: GraphQLSchema = makeExecutableSchema({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
uppercase: UppercaseDirective
}
});
Here is a link to source code using TypeScript

GraphQL "Cannot return null for non-nullable" [duplicate]

This question already has answers here:
Why does a GraphQL query return null?
(6 answers)
Closed 3 years ago.
Trying to make my first graphQL server, here's what I have written so far.
https://gist.github.com/tharakabimal/7f2947e805e69f67af2b633268db0406
Following error pops up on GraphQL when I try to filter the users by username.
Error on GraphQL
The error occurs in the users field in UserQueriesQL.js.
Is there anything wrong the way I pass arguments on the resolve functions?
user: {
type: UserType,
args: {
username: {
name: 'username',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: function(parentValue, args) {
return User.find( args ).exec();
}
As I am beginner into GraphQL, even I ran into this issue. After going through each file individually I found that I forgot to import into my resolvers
import User from './User';
**import Post from './Post';**
const resolvers = [User, **Posts**];
Maybe this will help!
user: {
type: UserType,
args: {
username: { type: new GraphQLNonNull(GraphQLString) }
},
resolve: function(parentValue, args) {
return User.find( args ).exec(); // User.find({username: 'some name'}).exec();
// will work as matches your mongoose schema
}
Previously, in the args you are providing an an object with nested object username so,
args: { // this won't match your mongoose schema field as it's nested object
username: {
name: 'username',
type: new GraphQLNonNull(GraphQLString)
}
}
so when the user queries and provides args then
your args would be { username: { name: 'abcd' } }
// args = {username: {name: 'abcd'}}
and resolve() is executing User.find({username: {name: 'abcd'}}).exec();
/* searching for username{} object, but
your mongoose schema is username: String */
which doesn't match your database fields, which will always return an empty array [],also which will not match your GraphQL field type, as it is GraphQLNonNull
after viewing the gist the problem is with rootquery
the problem is with rootquery
let RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
users: { type:UserQueries.users, resolve: UserQueries.users }
user: { type: UserQueries.user, resolve: UserQueries.user }
})
});

Categories