NestJS + Typeorm + Graphql: correct design pattern for DTO in nested relations - graphql

Say i have a Typeorm entity definition like this:
#Entity()
export class MyEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', { length: 500 })
name: string;
...
#OneToOne(type => DocumentEntity)
#JoinColumn()
myDoc: DocumentEntity;
#OneToMany(type => DocumentEntity, document => document.myEntity)
#JoinColumn()
otherDocs: DocumentEntity[];
...
}
so it has several entity relations, OneToMany/OneToOne
How do I approach this when crafting my DTOs?
Here I have an example DTO:
export class CreateMyEntityInputDto {
#IsString()
name: string;
...
#IsOptional()
myDoc: DocumentEntity;
#IsOptional()
otherDocs: DocumentEntity[];
....
}
I'm unclear on the best approach via Graphql
Current graphql interface:
####################
# #input
####################
input CreateDealInput {
name: String
...
myDoc: DocumentInput
otherDocs: [DocumentInput]
}
If I were designing a 'traditional' RESTful service, I would create my documents in the DB via a separate endpoint, wait for a success that returns documentID(s):int
then specify those ids as plain ints in the myEntity.myDoc / myEntity.otherDocs
fields when creating a new myEntity (at a separate endpoint).
Do i take the same approach here?
i.e. Do I create the documents entities in a separate query in graphql, parse out the created ids from the success response, then specify these int values in the DTO definition?
something like :
#IsOptional()
myDoc: int;
then, when creating the myEntity, load those (existing) document entities by id:int before finally saving via Typeorm?
Or do I pass all the document fields as nested entities in one big nested POST graphql query and use cascade to create them all?

Just ran into the same issue myself. My solution was to reference the nested entity by id. In your example this would be something like:
export class CreateMyEntityInputDto {
#IsString()
name: string;
...
#IsOptional()
myDocId: string;
#IsOptional()
otherDocIds: string[];
....
}
Rather than create nested entities in one mutation, this solution requires multiple.

Related

Relay pagination with GraphQL interfaces

I am building a GraphQL query that has an interface and multiple types implementing that interface for different data endpoints.
interface baseInterface {
id: ID
name: String
}
type type1 implements baseInterface {
id: ID
name: String
location: string
}
type type2 implements baseInterface {
id: ID
name: String
createdBy: string
}
type1 gets the data from service1 and type2 gets data from service2. The data is large from both the services so I can't pull all of the data in memory in one call. How does the Relay pagination work here? The two things I am trying to solve is:
how do GraphQL interfaces work with Relay pagination. When the client has this query,
query findTypes(first:10, after: $opaqueCursor ) {
id,
name
}
how does it render paginated data from both the sources? Would it build two paginated sets and use different cursors? And will it return 10 from each?
how does Relay pagination work with large data sets?

can't use type 'object' for Type-GraphQL field types

I would like to create a GraphQL layer for my NestJs API. So based on this interface holding a key/value pairs
export interface Mapping {
id: string;
value: object;
}
I created a DTO acting as a return type for the GraphQL queries
#ObjectType()
export class MappingDTO {
#Field(() => ID)
id: string;
#Field()
value: object;
}
So when I want to find all mappings I would come up with this
#Query(() => [MappingDTO])
public async mappings(): Promise<Mapping[]> {
return this.mappingsService.getMappings();
}
Unfortunately I can't use object for the value field. I'm getting this error
NoExplicitTypeError: You need to provide explicit type for
MappingDTO#value !
When chaning the type e.g. to string no error gets thrown on startup. The thing is that I can't specify an explicit type because my MongoDB stores objects holding "anything".
How can I fix my resolver or DTO to deal with an object type?
Edit:
Same for the resolver decorator. When I have this
#Query(() => Object)
public async getValueByKey(#Args('id') id: string): Promise<object> {
return this.mappingsService.getValueByKey(id);
}
I get this error
Error: Cannot determine GraphQL output type for getValueByKey
Sounds like you should be using a custom JSON scalar. Like this one.
import { GraphQLJSONObject } from 'graphql-type-json'
This can then either be used directly in the field decorator:
#Field(() => GraphQLJSONObject)
value: object
or you can leave the decorator as #Field() and add the appropriate scalarsMap to the options you pass to buildSchema. See the docs for additional details.
Just to point out the fact that the whole idea of GraphQL is declarative data fetching.
Also you can define custom types in GraphQL the scalar types don't have object as option, but you can define a type of yourself like this.
type customObject {
keyOne: Int!
KeyTwo: String!
...
key(n): ...
}
and use this type in Interfaces, Query, Mutations:
export interface Mapping {
id: string;
value: customObject;
}
You might think that you will have to define every key of the object you are storing in the mongoDB in this type customObject, but you don't have to. this is where the GraphQL declarative data fetching comes in. You only define those fields in the type (customObject) that you want to use on the front end, you don't need to fetch the complete object from DB.

How to use graphql type in other files for relationship without duplication

I am trying to separate all the tables in different directory. However, I am encountering some problems. Look at the ExpenseItem below. ExpenseType is under ExpenseType directory so graphql throws error. In order to solve that I need to add ExpenseType in ExpeseItem/typeDefs which is redundant.
ExpenseItem/typeDefs
type ExpenseItem {
id: ID!
name: String,
updatedAt: DateTime,
expenseType: [ExpenseType]
}
Is there any better way to stitch the schema?
I also tried something like this. However, graphql stop working.
When I query, it does not goes into the resolver, is there anything wrong on setup?
const schemas = makeExecutableSchema({
typeDefs: `
scalar JSON
scalar DateTime
type Query {
test: String
}
${typeDefs1}
${typeDefs2}
${typeDefs3}
${typeDefs4}
`,
resolvers: {
// Type resolvers
JSON: GraphQLJSON,
DateTime: GraphQLDateTime,
// schema resolvers
...resolvers1,
...resolvers2,
...resolvers3,
...resolvers4
}
})
const server = new ApolloServer({
schema,
context: { db }
})
You cannot merge your resolvers like this. Using the spread operator to merge an object results in a shallow merge. If the merged objects share any common properties (for example, Query), only the last merged property will be present in the resulting object. You need to deep merge the objects. You can do so yourself, or use something like lodash's merge.
Additionally, you can pass an array of strings as the typeDefs to makeExecutableSchema instead of combining them yourself.

combining resolvers for interface and concrete types

For some reason I'm having a hard time figuring out how to combine resolvers for a GraphQL interface and a type that implements said interface.
Say I have the following schema:
interface IPerson {
id: ID!
firstName: String!
lastName: String!
}
type ClubMember implements IPerson {
...IPerson fields
memberType: String!
memberSince: DateTime!
}
type StaffMember implements IPerson {
...IPerson fields
hireDate: DateTime!
reportsTo: StaffMember
}
extend type Query {
people(ids: [Int!]): [IPerson]
}
A full ClubMember query with all fields, such as:
query {
people(ids: [123456,234567,345678]) {
id
firstName
lastName
... on ClubMember {
memberType
memberSince
}
}
}
would produce a response like the following:
[
{
"id": 123456,
"firstName": "Member",
"lastName": "McMemberface",
"memberType": "VIP",
"memberSince": "2019-05-28T16:05:55+00:00"
},
...etc.
]
I've used makeExecutableSchema() from apollo-server with inheritResolversFromInterfaces: true, and I want to be able to make use of default resolvers for each interface/type by having the model classes backing IPerson, ClubMember, etc. return objects with only the fields relevant to each type, i.e., the model class for IPerson fetches only the fields required by IPerson, etc. That is, the response above would execute 2 SQL statements:
SELECT id, firstName, lastName FROM Contacts WHERE id IN(?);
and
SELECT contactId, memberType, memberSince FROM Members WHERE contactId IN(?);
Of course, I could get all the data in one SQL statement by doing a JOIN at the database level, but I really want to have one (and only one) way of resolving the fields required by IPerson, and let the other types augment that data with their own resolvers.
My question is, do I need to "join" the resulting objects together myself in the resolver for the people query type? E.g.
const resolvers = {
Query: {
people: function( parent, args, context, info ) {
let persons = context.models.Person.getByIds( args.ids );
let members = context.models.Member.getByIds( args.ids );
/*
return an array of {...person, ...member} where person.id === member.id
*/
}
}
}
Or is there some way that Apollo handles this for us? Do I want something like apollo-resolvers? The docs on unions and interfaces isn't super helpful; I have __resolveType on IPerson, but the docs don't specify how the fields for each concrete type are resolved. Is there a better way to achieve this with Dataloader, or a different approach?
I think this question is related to my issue, in that I don't want to fetch data for a concrete type if the query doesn't request any of that type's fields via a fragment. There's also this issue on Github.
Many thanks!
Edit:
__resolveType looks as follows:
{
IPerson: {
__resolveType: function ( parent, context, info ) {
if ( parent.memberType ) {
return 'ClubMember';
}
...etc.
}
}
}
This problem really isn't specific to Apollo Server or even GraphQL -- querying multiple tables and getting a single set of results, especially when you're dealing with multiple data models, is going to get tricky.
You can, of course, query each table separately and combine the results, but it's not particularly efficient. I think by far the easiest way to handle this kind of scenario is to create a view in your database, something like:
CREATE VIEW people AS
SELECT club_members.id AS id,
club_members.first_name AS first_name,
club_members.last_name AS last_name,
club_members.member_type AS member_type,
club_members.member_since AS member_since,
null AS hire_date,
null AS reports_to,
'ClubMember' AS __typename
FROM club_members
UNION
SELECT staff_members.id AS id,
staff_members.first_name AS first_name,
staff_members.last_name AS last_name,
null AS member_type,
null AS member_since,
staff_members.hire_date AS hire_date,
staff_members.reports_to AS reports_to
'StaffMember' AS __typename
FROM staff_members;
You can also just use a single table instead, but a view allows you to keep your data in separate tables and query them together. Now you can add a data model for your view and use that to query all "people".
Note: I've added a __typename column here for convenience -- by returning a __typename, you can omit specifying your own __resolveType function -- GraphQL will assign the appropriate type at runtime for you.

AWS Appsync: How can I create a resolver that retrieves the details for an array of identifiers?

This feels basic, so I would expect to find this scenario mentioned, but I have searched and can't find an example that matches my scenario. I have 2 end points (I am using HTTP data sources) that I'm trying to combine.
Class:
{
id: string,
students: [
<studentID1>,
<studentID2>,
...
]
}
and Student:
{
id: String,
lastName: String
}
What I would like is a schema that looks like this:
Student: {
id: ID!
lastName: String
}
Class: {
id: ID!,
studentDetails: [Student]
}
From reading, I know that I need some sort of resolver on Class.studentDetails that will return an array/List of student objects. Most of the examples I have seen show retrieving the list of Students based on class ID (ctx.source.id), but that won't work in this case. I need to call the students endpoint 1 time per student, passing in the student ID (I cannot fetch the list of students by class ID).
Is there a way to write a resolver for Class/studentDetails that loops through the student IDs in Class and calls my students endpoint for each one?
I was thinking something like this in the Request Mapping Template:
#set($studentDetails = [])
#foreach($student in $ctx.source.students)
#util.qr(list.add(...invoke web service to get student details...))
#end
$studentDetails
Edit: After reading Lisa Shon's comment below, I realized that the batch resolver for DynamoDB data sources that does this, but I don't see a way to do that for HTTP data sources.
It's not ideal, but you can create an intermediate type.
type Student {
id: ID!
lastName: String
}
type Class {
id: ID!,
studentDetails: [StudentDetails]
}
type StudentDetails {
student: Student
}
In your resolver template for Class, create a list of those student ids
#foreach ($student in $class.students)
$util.qr($studentDetails.add({"id": "$student.id"}))
#end
and add it to your response object. Then, hook a resolver to the student field of StudentDetails and you will then be able to use $context.source.id for the individual student API call. Each id will be broken out of the array and be its own web request.
I opened a case with AWS Support and was told that the only way they know to do this is to create a Lambda Resolver that:
Takes an array of student IDs
Calls the students endpoint for each one
Returns an array of student details information
Instead of calling your student endpoint in the response, use a pipeline resolver and stitch the response from different steps using stash, context (prev.result/result), etc.

Resources