واجهات GraphQL
التصفية والفرز والبحث في GraphQL
إمكانيات الاستعلام المتقدمة
يؤدي تنفيذ إمكانيات التصفية والفرز والبحث المرنة إلى جعل واجهة GraphQL API الخاصة بك أكثر قوة وسهولة في الاستخدام. تسمح هذه الميزات للعملاء بالاستعلام عن البيانات التي يحتاجونها بالضبط.
التصفية الأساسية باستخدام الوسائط
ابدأ بوسائط تصفية بسيطة في المخطط الخاص بك:
type Query {
posts(
category: String
status: PostStatus
authorId: ID
featured: Boolean
): [Post!]!
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
type Post {
id: ID!
title: String!
content: String!
category: String!
status: PostStatus!
featured: Boolean!
authorId: ID!
createdAt: DateTime!
}
محلل التصفية الأساسي
const resolvers = {
Query: {
posts: async (_, { category, status, authorId, featured }) => {
// بناء كائن التصفية
const filter = {};
if (category) filter.category = category;
if (status) filter.status = status;
if (authorId) filter.authorId = authorId;
if (featured !== undefined) filter.featured = featured;
return await Post.find(filter).sort({ createdAt: -1 });
}
}
};
// أمثلة على الاستعلامات:
// query {
// posts(category: "Technology", status: PUBLISHED) {
// id
// title
// }
// }
//
// query {
// posts(featured: true) {
// id
// title
// }
// }
نوع إدخال تصفية متقدم
استخدم أنواع الإدخال للتصفية الأكثر تعقيدًا:
type Query {
posts(filter: PostFilter, sort: PostSort): [Post!]!
}
input PostFilter {
category: String
status: PostStatus
authorId: ID
featured: Boolean
search: String
createdAfter: DateTime
createdBefore: DateTime
tags: [String!]
}
input PostSort {
field: PostSortField!
order: SortOrder = DESC
}
enum PostSortField {
CREATED_AT
UPDATED_AT
TITLE
VIEWS
}
enum SortOrder {
ASC
DESC
}
محلل التصفية المتقدم
const resolvers = {
Query: {
posts: async (_, { filter = {}, sort = {} }) => {
// بناء استعلام MongoDB
const query = {};
// مرشحات المساواة البسيطة
if (filter.category) query.category = filter.category;
if (filter.status) query.status = filter.status;
if (filter.authorId) query.authorId = filter.authorId;
if (filter.featured !== undefined) query.featured = filter.featured;
// مرشحات المصفوفة
if (filter.tags && filter.tags.length > 0) {
query.tags = { $in: filter.tags };
}
// مرشحات نطاق التاريخ
if (filter.createdAfter || filter.createdBefore) {
query.createdAt = {};
if (filter.createdAfter) {
query.createdAt.$gte = new Date(filter.createdAfter);
}
if (filter.createdBefore) {
query.createdAt.$lte = new Date(filter.createdBefore);
}
}
// البحث النصي
if (filter.search) {
query.$or = [
{ title: { $regex: filter.search, $options: 'i' } },
{ content: { $regex: filter.search, $options: 'i' } }
];
}
// بناء كائن الفرز
const sortField = sort.field || 'CREATED_AT';
const sortOrder = sort.order === 'ASC' ? 1 : -1;
const sortMap = {
CREATED_AT: 'createdAt',
UPDATED_AT: 'updatedAt',
TITLE: 'title',
VIEWS: 'views'
};
const sortObj = { [sortMap[sortField]]: sortOrder };
return await Post.find(query).sort(sortObj);
}
}
};
نصيحة: استخدم أنواع الإدخال للمرشحات المعقدة بدلاً من العديد من الوسائط الفردية. هذا يحافظ على مخططك نظيفًا وقابلاً للتوسيع.
نمط جملة Where
تنفيذ جمل where مرنة مشابهة لـ ORMs:
type Query {
posts(where: PostWhereInput, orderBy: PostOrderByInput): [Post!]!
}
input PostWhereInput {
id: IDFilter
title: StringFilter
content: StringFilter
category: StringFilter
status: PostStatus
featured: Boolean
views: IntFilter
createdAt: DateTimeFilter
author: UserWhereInput
AND: [PostWhereInput!]
OR: [PostWhereInput!]
NOT: PostWhereInput
}
input StringFilter {
equals: String
not: String
in: [String!]
notIn: [String!]
contains: String
startsWith: String
endsWith: String
}
input IntFilter {
equals: Int
not: Int
in: [Int!]
notIn: [Int!]
lt: Int
lte: Int
gt: Int
gte: Int
}
input DateTimeFilter {
equals: DateTime
not: DateTime
in: [DateTime!]
notIn: [DateTime!]
lt: DateTime
lte: DateTime
gt: DateTime
gte: DateTime
}
input PostOrderByInput {
createdAt: SortOrder
updatedAt: SortOrder
title: SortOrder
views: SortOrder
}
محلل جملة Where
function buildWhereClause(where) {
if (!where) return {};
const query = {};
// معالجة العوامل المنطقية
if (where.AND) {
query.$and = where.AND.map(buildWhereClause);
}
if (where.OR) {
query.$or = where.OR.map(buildWhereClause);
}
if (where.NOT) {
query.$not = buildWhereClause(where.NOT);
}
// معالجة مرشحات الحقول
Object.keys(where).forEach(key => {
if (['AND', 'OR', 'NOT'].includes(key)) return;
const filter = where[key];
// المساواة البسيطة
if (typeof filter !== 'object' || filter === null) {
query[key] = filter;
return;
}
// StringFilter
if (filter.equals) query[key] = filter.equals;
if (filter.not) query[key] = { $ne: filter.not };
if (filter.in) query[key] = { $in: filter.in };
if (filter.notIn) query[key] = { $nin: filter.notIn };
if (filter.contains) {
query[key] = { $regex: filter.contains, $options: 'i' };
}
if (filter.startsWith) {
query[key] = { $regex: `^${filter.startsWith}`, $options: 'i' };
}
if (filter.endsWith) {
query[key] = { $regex: `${filter.endsWith}$`, $options: 'i' };
}
// IntFilter / DateTimeFilter
if (filter.lt) query[key] = { ...query[key], $lt: filter.lt };
if (filter.lte) query[key] = { ...query[key], $lte: filter.lte };
if (filter.gt) query[key] = { ...query[key], $gt: filter.gt };
if (filter.gte) query[key] = { ...query[key], $gte: filter.gte };
});
return query;
}
const resolvers = {
Query: {
posts: async (_, { where, orderBy }) => {
const query = buildWhereClause(where);
let dbQuery = Post.find(query);
if (orderBy) {
const sort = {};
Object.keys(orderBy).forEach(key => {
sort[key] = orderBy[key] === 'ASC' ? 1 : -1;
});
dbQuery = dbQuery.sort(sort);
}
return await dbQuery;
}
}
};
ملاحظة: توفر جمل where المعقدة إمكانيات تصفية مشابهة لـ Prisma، مما يجعل واجهة برمجة التطبيقات الخاصة بك أكثر قوة ومرونة.
البحث بالنص الكامل
تنفيذ البحث بالنص الكامل باستخدام فهارس النص في MongoDB:
// إنشاء فهرس نص في MongoDB
// db.posts.createIndex({ title: "text", content: "text" })
type Query {
searchPosts(query: String!, limit: Int = 20): [PostSearchResult!]!
}
type PostSearchResult {
post: Post!
score: Float!
}
const resolvers = {
Query: {
searchPosts: async (_, { query, limit }) => {
const results = await Post.find(
{ $text: { $search: query } },
{ score: { $meta: 'textScore' } }
)
.sort({ score: { $meta: 'textScore' } })
.limit(limit);
return results.map(post => ({
post,
score: post.score
}));
}
}
};
// مثال على الاستعلام:
// query {
// searchPosts(query: "GraphQL tutorial", limit: 10) {
// post {
// id
// title
// }
// score
// }
// }
دمج المرشحات والفرز والترقيم
type Query {
posts(
filter: PostFilter
sort: PostSort
pagination: PaginationInput
): PostConnection!
}
input PaginationInput {
first: Int
after: String
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
const resolvers = {
Query: {
posts: async (_, { filter, sort, pagination }) => {
// بناء الاستعلام من المرشح
const query = buildFilterQuery(filter);
// بناء كائن الفرز
const sortObj = buildSortObject(sort);
// تطبيق الترقيم
let dbQuery = Post.find(query).sort(sortObj);
if (pagination?.after) {
const cursor = decodeCursor(pagination.after);
query._id = { $lt: cursor };
}
const limit = pagination?.first || 10;
const posts = await dbQuery.limit(limit + 1);
// التحقق من المزيد من النتائج
const hasMore = posts.length > limit;
const nodes = hasMore ? posts.slice(0, limit) : posts;
// الحصول على العدد الإجمالي
const totalCount = await Post.countDocuments(query);
return {
edges: nodes.map(post => ({
node: post,
cursor: encodeCursor(post._id)
})),
pageInfo: {
hasNextPage: hasMore,
hasPreviousPage: !!pagination?.after,
startCursor: nodes[0] ? encodeCursor(nodes[0]._id) : null,
endCursor: nodes[nodes.length - 1]
? encodeCursor(nodes[nodes.length - 1]._id)
: null
},
totalCount
};
}
}
};
// مثال على استعلام يجمع كل شيء:
// query {
// posts(
// filter: {
// category: "Technology"
// status: PUBLISHED
// createdAfter: "2026-01-01"
// search: "GraphQL"
// }
// sort: { field: VIEWS, order: DESC }
// pagination: { first: 20 }
// ) {
// edges {
// node {
// id
// title
// views
// }
// }
// pageInfo {
// hasNextPage
// endCursor
// }
// totalCount
// }
// }
تحذير: يمكن أن تكون المرشحات المعقدة مكلفة. أضف دائمًا فهارس قاعدة البيانات للحقول المصفاة بشكل متكرر وراقب أداء الاستعلام.
تمرين:
- نفذ استعلام
usersمع مرشحات للدور والحالة ونطاق تاريخ التسجيل والبحث - أنشئ نظام جملة where يدعم عوامل منطقية AND و OR و NOT
- أضف بحث بالنص الكامل مع تسجيل الملاءمة لمنشورات المدونة
- ابنِ استعلام منتجات يدعم التصفية حسب نطاق السعر والفئة والتقييم والتوفر
- اجمع بين التصفية والفرز والترقيم القائم على المؤشر في استعلام واحد