Cannot fetch data in Graphql Relay - javascript

I'm learning Relay to use in a React-Relay project. After my research and learning on the internet, I've run into problems with my graphql schema and resolvers. I can't seem to figure out what resolvers do I need and how go about it correctly. If someone can point me in the right direction, that would be great. I have attached my code below. All I'm trying to do is fetch elements of a list using the relay graphql server specification.
Graphql Schema
interface Node {
id: ID!
}
type Link implements Node {
id: ID!
title: String!
description: String!
}
type LinkConnection {
edges: [LinkEdge]
pageInfo: PageInfo!
}
type LinkEdge {
cursor: String!
node: Link
}
type Query {
links(after: String, before: String, first: Int, last: Int): LinkConnection
node(id: ID!): Node
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Resolvers
const resolvers = {
Query,
Node: {
__resolveType(node, context, info){
if(node.title){
return 'Link'
}
return null
}
},
}
Query Resolver
const node = async (parent, args, {prisma}) => {
try{
console.log('hit')
const data = await prisma.link.findUnique({
where: {
id: Number(args.id)
}
})
console.log(data)
return data
} catch(err){
return err
}
}
export default {
node,
}
P.S. Im using Apollo Server and Prisma under the hood
Edit:
I solved this issue by realizing that the resolves are invoked in the same order the schema is nested. So by writing separate resolvers for each type and passing the information in parent argument, things worked.

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' }

Import build schema from .graphql file

I want to create a GraphQL API with the following schema
app.use(
"/graphql",
graphQlHttp({
schema: buildSchema(`
type Event {
_id: ID!
title: String!
description: String!
price: Float!
}
input EventInput {
title: String!
description: String!
price: Float!
}
type QueryResolver {
getEvents: [Event!]!
}
type MutationResolver {
createEvent(eventInput: EventInput): [Event!]!
}
schema {
query: QueryResolver
mutation: MutationResolver
}
`),
rootValue: {}
})
);
Currently I am using it as a string in in the my main file. I want to separate it out in a graphql file. One way can be that we can read the file but I think it will not be efficient.
Can you please tell me how can I do this?
You can add this node module:
https://github.com/ardatan/graphql-import-node
Then move your schema to something like mySchema.graphql.
Then, in JS:
const mySchema = require('./mySchema.graphql');
or in TypeScript:
import * as mySchema from './mySchema.graphql';
I don't have the reputation to comment so I'll put this here.
If anyone following #Tim O'Connell's answer is wondering how to pass the imported Schema to BuildSchema(), since it's not a string anymore but a DocumentNode, you actually need to use the function BuildASTSchema().
Note that BuildSchema() is just a wrapper around BuildASTSchema() that parses the given string into a DocumentNode. (source)
So to recap, do something like this :
import 'graphql-import-node';
import { buildASTSchema } from 'graphql';
import * as mySchema from './schema.graphql';
export = buildASTSchema(mySchema);
Just import all of your schemas using the gql module. Install it by:
npm i graphql-tag
You can use the graphql-tool.
const { loadSchemaSync } = require("#graphql-tools/load");
const { GraphQLFileLoader } = require("#graphql-tools/graphql-file-loader");
const { addResolversToSchema } = require("#graphql-tools/schema");
const { join } = require("path");
const schemaWithResolvers = addResolversToSchema({
schema: loadSchemaSync(join(__dirname, "./(your graphql file).graphql"), {
loaders: [new GraphQLFileLoader()],
}),
resolvers: {},
});
And, your graphql file is
type Event {
_id: ID!
title: String!
description: String!
price: Float!
}
input EventInput {
title: String!
description: String!
price: Float!
}
type QueryResolver {
getEvents: [Event!]!
}
type MutationResolver {
createEvent(eventInput: EventInput): [Event!]!
}
schema {
query: QueryResolver
mutation: MutationResolver
}
Then, you just need to use schemaWithResolvers.builldSchema is unnecessary .
app.use(
"/graphql",
graphQlHttp({
schema: schemaWithResolvers
})
);

Object as input variable in mutation: GraphQL - Apollo - React

I have a React client-side project and a Node.js/GraphQL api in two separate repo's.
In my React app, I want to pass an object as variable type into my mutation. Here's how my mutation looks like:
export const CREATE_SPEAKER = gql`
input Expertise {
title: String!
domain: String!
}
mutation CreateSpeaker(
$name: String!
$age: String!
$nationality: String!
$avatar: String!
$expertise: Expertise!
) {
createSpeaker(
speakerInput: {
name: $name
age: $age
nationality: $nationality
avatar: $avatar
expertise: $expertise
}
) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}
`;
In my Node.js project I have the following schema:
input SpeakerInput {
name: String!
age: String!
expertise: ExpertiseInput!
nationality: String!
avatar: String
}
input ExpertiseInput {
title: String!
domain: String!
}
And my resolver:
createSpeaker: async args => {
const { name, age, nationality, avatar, expertise } = args.speakerInput;
const newSpeaker = new Speaker({
name,
age,
nationality,
avatar,
expertise: {
title: expertise.title,
domain: expertise.domain
}
});
try {
return await newSpeaker.save();
} catch (error) {
throw ("Failed to create speaker:: ", error);
}
}
But I'm getting the following error when trying to create the speaker:
Uncaught (in promise) Invariant Violation: Schema type definitions not
allowed in queries. Found: "InputObjectTypeDefinition"
Any suggestions/ideas how to do this?
You can't define additional types when sending requests to a GraphQL service and you don't need to -- just use the types you've already defined on the server (in this case ExpertiseInput:
$expertise: ExpertiseInput!
However, there's no need to use this many variables in the first place:
mutation CreateSpeaker($input: SpeakerInput!) {
createSpeaker(speakerInput: $input) {
name
age
nationality
avatar
expertise {
title
domain
}
}
}

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.

What does the interfaces in GraphQLInterfaceType?

I have the following the code snippet:
export const NodeInterface = new GraphQLInterfaceType({
name: 'Node',
fields: {
id: {
type: new GraphQLNonNull(GraphQLID)
}
},
resolveType: (source) => {
if (source.__tableName === tables.users.getName()) {
return UserType;
}
return PostType;
}
});
and a GraphQLObjectType that is using the interface:
export const PostType = new GraphQLObjectType({
name: 'Post',
interfaces: [ NodeInterface ],
fields: {
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: resolveId
},
createdAt: {
type: new GraphQLNonNull(GraphQLString),
},
body: {
type: new GraphQLNonNull(GraphQLString)
}
}
});
For what do I have to define an interface?
In GraphQL, interfaces fulfill two purposes:
They ensure that the types implementing them also implement specific fields. For example, the Node interface here has an id field -- that means any type that implements the Node interface will also need to have an id field (and that id will need to be an ID scalar like in the interface) The same goes for arguments on those -- any arguments on the fields in the interface will also have to exist on the matching fields in the implementing type.
They can be used when two or more types are expected for a field. A field will always resolve to exactly one type or scalar, however, by using interfaces (or unions) we indicate in our schema that the field could resolve to one of a set of types.
So let's say we have a Node like in your snippet, some types that implement it, and a query that returns a Node:
interface Node {
id: ID!
}
type Foo implements Node {
id: ID!
someFooField: String!
someOtherFooField: Int!
}
type Bar implements Node {
id: ID!
someBarField: String!
someOtherFooField: Int!
}
type Query {
getNode(id: ID!): Node!
}
In our example, getNode could resolve to either a Foo or a Bar. When we write our query, we don't know which one will be resolved. But because we know the id field is required by the interface, we can write a query like this:
query OperationName {
getNode(id: "SOME_ID"){
id
}
}
If we need to query someBarField as well, though, we can't do this:
query OperationName {
getNode(id: "SOME_ID"){
id
someBarField
}
}
because Foo doesn't have that field. Instead we have to utilize a fragment, like this:
query OperationName {
getNode(id: "SOME_ID"){
id
... on Bar {
someBarField
}
}
}
Then someBarField will be returned, but only if the field resolves to the type Bar. If it's a Foo, only id will be returned. Similarly, you can request non-shared fields from any type implementing the same interface:
query OperationName {
getNode(id: "SOME_ID"){
id
... on Bar {
someBarField
}
... on Foo {
someFooField
}
}
}
Last but not least, it should be mentioned that unions work in a very similar fashion. However, unlike interfaces, there are no shared fields defined for a Union, so a type does not "implement" a union, it just is part of one. That means when requesting a field that returns a union, you'll always have to use fragments since there are no shared fields to request.

Categories