Я использую Apollo Server 4 и Prisma 5 для создания простого проекта ECOM. В настоящий момент я пытаюсь настроить свои преобразователи так, чтобы они могли двунаправленно запрашивать пользователей + их комментарии... а также запрашивать комментарии как верхний уровень и возвращать связанных с ними пользователей. В базе данных таблица Comment
имеет связанное отношение user_id
FK с таблицей User
. User
таблица НЕ имеет идентификатора комментариев, поэтому связь один -> многие и многие -> один.
Когда я пытаюсь запросить пользователя, комментарии — это null
, если только я не напишу для них отдельный преобразователь, а затем не использую форму Prisma для include
отношений. Я не хочу следовать этому, поскольку при использовании gql это похоже на антишаблон. Чего я хочу добиться, так это иметь userByEmail()
преобразователь на моем верхнем уровне Query{}
из моего SDL... а затем иметь объект Comment: {}
2-го уровня в моих преобразователях, чтобы затем соответствующий User
был аргументом parent
, поступающим в преобразователь... это больше зависит от GQL, чем от БД.
Соответствующие модели призмы:
model User {
id String @id @default(uuid())
username String @unique
email String @unique
password String
first_name String
last_name String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted Boolean @default(false)
comments Comment[]
ratings Rating[]
orders Order[]
UserAddress UserAddress[]
}
model Comment {
id String @id @default(uuid())
user User @relation(fields: [user_id], references: [id])
user_id String // Change type to String
product Product @relation(fields: [product_id], references: [id])
product_id String
content String
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted Boolean @default(false)
}
Соответствующие типы Schema.graphql:
type Query {
"Get a user by their email"
userByEmail(email: String): User!
}
"This type represents a user in the system"
type User {
"Unique identifier for the user"
id: ID!
"Username for login"
username: String!
"User's email address"
email: String!
"User's first name"
first_name: String!
"User's last name (optional)"
last_name: String!
"List of comments authored by the user (optional)"
comments: [Comment]
"List of ratings created by the user (optional)"
ratings: [Rating]
"List of orders placed by the user (optional)"
orders: [Order]
"List of user's addresses"
addresses: [Address]
}
type Comment {
"Unique identifier for the comment"
id: ID!
"Content of the comment"
content: String!
"User who authored the comment"
user: User
"Product this comment is associated with"
product: Product
}
резольверы.ts
import { getUserByEmail, getCommentsByUserId } from '../../dataAccessLayer/orm/user';
import { User } from '../../generated/types';
// Use intersection type to combine Resolvers and Query
export const resolvers = {
Query: {
userByEmail: async (_parent: unknown, { email }: { email: string }) => {
console.info('parent', email);
const user = await getUserByEmail(email); // Already includes comments
return user;
},
},
Comment: {
userComments: (parent: User) => {
console.info('parent', parent);
const userId = parent.id;
return getCommentsByUserId(userId);
},
},
};
Запросы к базе данных ORM
import prisma from '../prismaClient';
export async function getUserByEmail(email: string) {
return await prisma.user.findUnique({
where: {
email,
},
});
}
export async function getCommentsByUserId(id: string) {
return await prisma.comment.findMany({
where: {
user_id: id,
},
include: {
user: true, // Include user relation
},
});
}
Когда я запускаю свой проект, я получаю эту ошибку:
Error: Comment.userComments defined in resolvers, but not in schema
[0] at addResolversToSchema
Как я могу правильно запросить подполя, используя рекомендованный Apollo подход с использованием аргументов (parent, args, context, info)
, где parent
должен быть связанный пользователь, первоначально запрашиваемый при попытке получить комментарии? Таким образом, я не объединяю запросы к базе данных на уровне orm.
Спасибо!
Взгляните на примеры в документации сервера Apollo, как реализовать преобразователи
В схеме вы уже включили свойство comments
в тип User
.
type User {
id: ID!
username: String!
"List of comments authored by the user (optional)"
comments: [Comment]
# ... other fields
}
вы также определили userByEmail
для получения пользователя типа верхнего уровня Query
type Query {
userByEmail(email: String): User!
}
Далее вам необходимо реализовать преобразователи для этой схемы. Для типа Query
вам необходимо реализовать преобразователь для метода userByEmail
. Вы сделали эту часть
import { getUserByEmail } from '../../dataAccessLayer/orm/user';
import { User } from '../../generated/types';
// Use intersection type to combine Resolvers and Query
export const resolvers = {
Query: {
userByEmail: async (_, { email }: { email: string }) => {
console.info('parent', email);
const user = await getUserByEmail(email); // Already includes comments
return user;
},
},
};
Следующее, что вам нужно сделать, это определить преобразователь для свойства comments
объекта User
, который даст вам именно то, что вы хотите: комментарии пользователей.
import { getUserByEmail, getCommentsByUserId } from '../../dataAccessLayer/orm/user';
import { User } from '../../generated/types';
// Use intersection type to combine Resolvers and Query
export const resolvers = {
Query: {
userByEmail: async (_, { email }: { email: string }) => {
const user = await getUserByEmail(email); // Already includes comments
return user;
},
},
// User. not Comment.
User: {
// The parent entity for comments field is user
comments: async (parent: User, _: any) {
if (parent.comments !== undefined) {
// you mentioned that user fetched with userByEmail already
// includes comments, so you might not even need to refetch it
return parent.comments
}
const comments = await getCommentsByUserId(user.id)
return comments;
},
}
};
А это пример запроса, совместимого с нашей схемой
query userWithComments($email: String!) {
userByEmail(email: $email) {
id
username
# some other fields
comments {
id
content
}
}
}