مقدمة: لماذا يعتبر GraphQL و Go ثنائياً مثالياً
\nفي تطوير الويب الحديث، أصبح GraphQL معياراً لإنشاء واجهات برمجة تطبيقات (API) مرنة وفعالة. وبالاقتران مع لغة Go (Golang)، المعروفة بأدائها العالي وبساطتها، تحصل على أداة قوية لتطوير الواجهة الخلفية (backend). في هذا الدليل التعليمي خطوة بخطوة، سنقوم بإنشاء GraphQL API كامل بلغة Go من الصفر، باستخدام المكتبة الشهيرة gqlgen.
ستتعلم كيفية إعداد المشروع، وتحديد المخطط (schema)، وتنفيذ المحللّات (resolvers)، وإضافة middleware للمصادقة. في نهاية المقال، سيكون لديك API يعمل وجاهز للتكامل مع أي واجهة أمامية (frontend).
\n\n1. إعداد المشروع وتثبيت التبعيات
\nأولاً، سنقوم بإنشاء وحدة Go جديدة وتثبيت الحزم اللازمة. تأكد من تثبيت Go الإصدار 1.18 أو أعلى.
\nmkdir graphql-go-tutorial\ncd graphql-go-tutorial\ngo mod init graphql-go-tutorial\ngo get github.com/99designs/gqlgen@latest\ngo get github.com/99designs/gqlgen/graphql/introspection@latest\nالآن، سنقوم بتهيئة gqlgen في المشروع. هذا الأمر سينشئ هيكل الملفات الأساسي:
go run github.com/99designs/gqlgen init\nبعد التنفيذ، سترى الهيكل التالي:
\n- \n
graph/— دليل يحتوي على ملفات المخطط والمحللّات \ngraph/schema.graphqls— تعريف مخطط GraphQL \ngraph/resolver.go— نماذج أولية للمحللّات \nserver.go— نقطة الدخول للتطبيق \ngqlgen.yml— إعدادات المولّد (generator) \n
2. تعريف مخطط GraphQL
\nافتح ملف graph/schema.graphqls وحدد نموذج البيانات الخاص بنا. على سبيل المثال، سننشئ نظاماً بسيطاً لإدارة المهام (Task Manager):
type Task {\n id: ID!\n title: String!\n description: String\n status: TaskStatus!\n createdAt: Time!\n}\n\nenum TaskStatus {\n TODO\n IN_PROGRESS\n DONE\n}\n\ntype Query {\n tasks(status: TaskStatus): [Task!]!\n task(id: ID!): Task\n}\n\ntype Mutation {\n createTask(title: String!, description: String): Task!\n updateTask(id: ID!, title: String, description: String, status: TaskStatus): Task!\n deleteTask(id: ID!): Boolean!\n}\n\nscalar Time\nبعد حفظ المخطط، قم بتشغيل توليد الكود:
\ngo run github.com/99designs/gqlgen generate\nسينشئ هذا الأمر ملف graph/model/models_gen.go مع هياكل Go وسيُحدّث المحللّات.
3. تنفيذ المحللّات ومنطق الأعمال
\nالآن، سنقوم بتنفيذ المنطق في ملف graph/schema.resolvers.go. أولاً، سننشئ مخزناً بسيطاً في الذاكرة (in-memory store):
// في graph/resolver.go سنضيف المخزن\ntype Resolver struct {\n tasks []*model.Task\n mu sync.Mutex\n}\n\nfunc NewResolver() *Resolver {\n return &Resolver{\n tasks: make([]*model.Task, 0),\n }\n}\nالآن، سنقوم بتنفيذ المحللّات لـ Query و Mutation:
\n// Query.Task\nfunc (r *queryResolver) Task(ctx context.Context, id string) (*model.Task, error) {\n r.mu.Lock()\n defer r.mu.Unlock()\n \n for _, task := range r.tasks {\n if task.ID == id {\n return task, nil\n }\n }\n return nil, fmt.Errorf("task not found")\n}\n\n// Mutation.CreateTask\nfunc (r *mutationResolver) CreateTask(ctx context.Context, title string, description *string) (*model.Task, error) {\n r.mu.Lock()\n defer r.mu.Unlock()\n \n task := &model.Task{\n ID: uuid.New().String(),\n Title: title,\n Description: description,\n Status: model.TaskStatusTodo,\n CreatedAt: time.Now(),\n }\n \n r.tasks = append(r.tasks, task)\n return task, nil\n}\n\n// Mutation.UpdateTask\nfunc (r *mutationResolver) UpdateTask(ctx context.Context, id string, title *string, description *string, status *model.TaskStatus) (*model.Task, error) {\n r.mu.Lock()\n defer r.mu.Unlock()\n \n for _, task