واجهات GraphQL

التصفية والفرز والبحث في GraphQL

19 دقيقة الدرس 14 من 35

إمكانيات الاستعلام المتقدمة

يؤدي تنفيذ إمكانيات التصفية والفرز والبحث المرنة إلى جعل واجهة 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 // } // }
تحذير: يمكن أن تكون المرشحات المعقدة مكلفة. أضف دائمًا فهارس قاعدة البيانات للحقول المصفاة بشكل متكرر وراقب أداء الاستعلام.
تمرين:
  1. نفذ استعلام users مع مرشحات للدور والحالة ونطاق تاريخ التسجيل والبحث
  2. أنشئ نظام جملة where يدعم عوامل منطقية AND و OR و NOT
  3. أضف بحث بالنص الكامل مع تسجيل الملاءمة لمنشورات المدونة
  4. ابنِ استعلام منتجات يدعم التصفية حسب نطاق السعر والفئة والتقييم والتوفر
  5. اجمع بين التصفية والفرز والترقيم القائم على المؤشر في استعلام واحد