بناء واجهات REST API باستخدام Go و Fiber: دليل عملي للمبتدئين

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

Go هي اللغة التي تقف خلف Docker و Kubernetes ومعظم البنية التحتية السحابية — و Fiber يجعلها في متناول مطوّري الويب. مستوحى من Express.js ومبني على Fasthttp، يوفر Fiber أداءً يصل إلى 10 أضعاف Node.js مع أمان الذاكرة والتزامن المبني على goroutines. في هذا الدرس، ستبني واجهة REST API متكاملة من الصفر.

ماذا ستبني؟

نظام إدارة المهام (Task Manager) متكامل يتضمّن:

  • عمليات CRUD كاملة للمهام (إنشاء، قراءة، تحديث، حذف)
  • معالجة طلبات واستجابات JSON
  • قاعدة بيانات PostgreSQL مع GORM (أشهر ORM في Go)
  • معالجة أخطاء منظّمة برموز HTTP مناسبة
  • وسطاء (Middleware) للتسجيل، CORS، وتحديد المعدّل
  • التحقق من صحة المدخلات
  • إعدادات مبنية على متغيّرات البيئة
  • اختبارات تكامل

المتطلبات المسبقة

قبل البدء، تأكد من توفر التالي:

  • Go 1.22+ مثبّتة عبر go.dev/dl
  • PostgreSQL 15+ تعمل محلياً أو عبر Docker
  • فهم أساسي لبروتوكول HTTP ومفاهيم REST
  • محرر أكواد (يُنصح بـ VS Code مع إضافة Go)
  • سطر أوامر (Terminal)

جديد على Go؟ يجب أن تكون مرتاحاً مع الهياكل (Structs)، الواجهات (Interfaces)، معالجة الأخطاء، و goroutines. الجولة الرسمية Tour of Go هي نقطة بداية ممتازة.


لماذا Fiber؟

قبل الغوص في التفاصيل، دعنا نفهم لماذا يتميّز Fiber في منظومة Go:

الميزةFiberGinEchoChi
مستوحى منExpress.jsMartiniSinatraالمكتبة القياسية
محرك HTTPFasthttpnet/httpnet/httpnet/http
الأداءالأسرعسريعسريعسريع
صعوبة التعلممنخفضة (شبيه بـ Express)منخفضةمتوسطةمنخفضة
الوسطاءمدمجة + مخصصةمن المجتمعمدمجةمتوافقة مع stdlib

واجهة Fiber الشبيهة بـ Express تعني أنه إذا كنت قد بنيت خوادم Node.js من قبل، ستشعر وكأنك في بيتك — لكن مع أداء Go وأمان الأنواع.


الخطوة 1: إعداد المشروع

أنشئ مجلد المشروع وقم بتهيئة وحدة Go:

mkdir task-api && cd task-api
go mod init github.com/yourusername/task-api

ثبّت الاعتماديات الأساسية:

go get github.com/gofiber/fiber/v2
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/joho/godotenv

أنشئ هيكل المشروع:

mkdir -p cmd/api internal/{models,handlers,database,middleware,config}
touch cmd/api/main.go
touch .env

سيبدو مشروعك هكذا:

task-api/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── database/
│   │   └── database.go
│   ├── handlers/
│   │   └── task.go
│   ├── middleware/
│   │   └── middleware.go
│   └── models/
│       └── task.go
├── .env
├── go.mod
└── go.sum

الخطوة 2: إعداد متغيّرات البيئة

أنشئ ملف .env:

DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=taskapi
APP_PORT=3000

أنشئ محمّل الإعدادات في internal/config/config.go:

package config
 
import (
	"log"
	"os"
 
	"github.com/joho/godotenv"
)
 
type Config struct {
	DBHost     string
	DBPort     string
	DBUser     string
	DBPassword string
	DBName     string
	AppPort    string
}
 
func LoadConfig() *Config {
	err := godotenv.Load()
	if err != nil {
		log.Println("No .env file found, using system environment variables")
	}
 
	return &Config{
		DBHost:     getEnv("DB_HOST", "localhost"),
		DBPort:     getEnv("DB_PORT", "5432"),
		DBUser:     getEnv("DB_USER", "postgres"),
		DBPassword: getEnv("DB_PASSWORD", "postgres"),
		DBName:     getEnv("DB_NAME", "taskapi"),
		AppPort:    getEnv("APP_PORT", "3000"),
	}
}
 
func getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

الخطوة 3: تعريف نموذج البيانات

أنشئ نموذج المهمة في internal/models/task.go:

package models
 
import (
	"time"
 
	"gorm.io/gorm"
)
 
type TaskStatus string
 
const (
	StatusPending    TaskStatus = "pending"
	StatusInProgress TaskStatus = "in_progress"
	StatusCompleted  TaskStatus = "completed"
)
 
type Task struct {
	ID          uint           `json:"id" gorm:"primaryKey"`
	Title       string         `json:"title" gorm:"size:255;not null"`
	Description string         `json:"description" gorm:"type:text"`
	Status      TaskStatus     `json:"status" gorm:"size:20;default:pending"`
	Priority    int            `json:"priority" gorm:"default:0"`
	DueDate     *time.Time     `json:"due_date,omitempty"`
	CreatedAt   time.Time      `json:"created_at"`
	UpdatedAt   time.Time      `json:"updated_at"`
	DeletedAt   gorm.DeletedAt `json:"-" gorm:"index"`
}
 
type CreateTaskRequest struct {
	Title       string     `json:"title"`
	Description string     `json:"description"`
	Priority    int        `json:"priority"`
	DueDate     *time.Time `json:"due_date"`
}
 
type UpdateTaskRequest struct {
	Title       *string     `json:"title"`
	Description *string     `json:"description"`
	Status      *TaskStatus `json:"status"`
	Priority    *int        `json:"priority"`
	DueDate     *time.Time  `json:"due_date"`
}
 
func (r *CreateTaskRequest) Validate() map[string]string {
	errors := make(map[string]string)
	if r.Title == "" {
		errors["title"] = "Title is required"
	}
	if len(r.Title) > 255 {
		errors["title"] = "Title must be 255 characters or less"
	}
	if r.Priority < 0 || r.Priority > 5 {
		errors["priority"] = "Priority must be between 0 and 5"
	}
	return errors
}

لاحظ كيف نستخدم gorm.DeletedAt للحذف الناعم — لن تُحذف السجلات نهائياً من قاعدة البيانات، مما يسهّل استعادتها لاحقاً.


الخطوة 4: الاتصال بقاعدة البيانات

أنشئ طبقة قاعدة البيانات في internal/database/database.go:

package database
 
import (
	"fmt"
	"log"
 
	"github.com/yourusername/task-api/internal/config"
	"github.com/yourusername/task-api/internal/models"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)
 
var DB *gorm.DB
 
func Connect(cfg *config.Config) {
	dsn := fmt.Sprintf(
		"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPassword, cfg.DBName,
	)
 
	var err error
	DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}
 
	log.Println("Database connected successfully")
 
	err = DB.AutoMigrate(&models.Task{})
	if err != nil {
		log.Fatalf("Failed to migrate database: %v", err)
	}
 
	log.Println("Database migrated successfully")
}

أنشئ قاعدة البيانات قبل تشغيل التطبيق:

createdb taskapi
# أو باستخدام psql:
# psql -U postgres -c "CREATE DATABASE taskapi;"

الخطوة 5: بناء معالجات الـ API

هذا هو جوهر واجهتك. أنشئ internal/handlers/task.go:

package handlers
 
import (
	"strconv"
 
	"github.com/gofiber/fiber/v2"
	"github.com/yourusername/task-api/internal/database"
	"github.com/yourusername/task-api/internal/models"
)
 
// ListTasks يُرجع جميع المهام مع خيارات التصفية
func ListTasks(c *fiber.Ctx) error {
	var tasks []models.Task
 
	query := database.DB
 
	// التصفية حسب الحالة إذا وُجدت
	if status := c.Query("status"); status != "" {
		query = query.Where("status = ?", status)
	}
 
	// التصفية حسب الأولوية إذا وُجدت
	if priority := c.Query("priority"); priority != "" {
		query = query.Where("priority = ?", priority)
	}
 
	// التصفّح (Pagination)
	page, _ := strconv.Atoi(c.Query("page", "1"))
	limit, _ := strconv.Atoi(c.Query("limit", "10"))
	offset := (page - 1) * limit
 
	var total int64
	query.Model(&models.Task{}).Count(&total)
 
	result := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&tasks)
	if result.Error != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to fetch tasks",
		})
	}
 
	return c.JSON(fiber.Map{
		"data":  tasks,
		"total": total,
		"page":  page,
		"limit": limit,
	})
}
 
// GetTask يُرجع مهمة واحدة حسب المعرّف
func GetTask(c *fiber.Ctx) error {
	id := c.Params("id")
 
	var task models.Task
	result := database.DB.First(&task, id)
	if result.Error != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Task not found",
		})
	}
 
	return c.JSON(task)
}
 
// CreateTask يُنشئ مهمة جديدة
func CreateTask(c *fiber.Ctx) error {
	req := new(models.CreateTaskRequest)
 
	if err := c.BodyParser(req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}
 
	// التحقق من الصحة
	if errors := req.Validate(); len(errors) > 0 {
		return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
			"errors": errors,
		})
	}
 
	task := models.Task{
		Title:       req.Title,
		Description: req.Description,
		Priority:    req.Priority,
		DueDate:     req.DueDate,
		Status:      models.StatusPending,
	}
 
	result := database.DB.Create(&task)
	if result.Error != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Failed to create task",
		})
	}
 
	return c.Status(fiber.StatusCreated).JSON(task)
}
 
// UpdateTask يُحدّث مهمة موجودة
func UpdateTask(c *fiber.Ctx) error {
	id := c.Params("id")
 
	var task models.Task
	if err := database.DB.First(&task, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Task not found",
		})
	}
 
	req := new(models.UpdateTaskRequest)
	if err := c.BodyParser(req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "Invalid request body",
		})
	}
 
	// تطبيق التحديثات الجزئية
	if req.Title != nil {
		task.Title = *req.Title
	}
	if req.Description != nil {
		task.Description = *req.Description
	}
	if req.Status != nil {
		task.Status = *req.Status
	}
	if req.Priority != nil {
		task.Priority = *req.Priority
	}
	if req.DueDate != nil {
		task.DueDate = req.DueDate
	}
 
	database.DB.Save(&task)
 
	return c.JSON(task)
}
 
// DeleteTask يحذف مهمة (حذف ناعم)
func DeleteTask(c *fiber.Ctx) error {
	id := c.Params("id")
 
	var task models.Task
	if err := database.DB.First(&task, id).Error; err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": "Task not found",
		})
	}
 
	database.DB.Delete(&task)
 
	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"message": "Task deleted successfully",
	})
}

الخطوة 6: إضافة الوسطاء (Middleware)

أنشئ internal/middleware/middleware.go للتسجيل، CORS، وتحديد المعدّل:

package middleware
 
import (
	"log"
	"time"
 
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/limiter"
)
 
func SetupMiddleware(app *fiber.App) {
	// CORS
	app.Use(cors.New(cors.Config{
		AllowOrigins: "*",
		AllowMethods: "GET,POST,PUT,PATCH,DELETE",
		AllowHeaders: "Origin,Content-Type,Accept,Authorization",
	}))
 
	// تحديد المعدّل: 100 طلب في الدقيقة
	app.Use(limiter.New(limiter.Config{
		Max:               100,
		Expiration:        1 * time.Minute,
		LimiterMiddleware: limiter.SlidingWindow{},
	}))
 
	// تسجيل الطلبات
	app.Use(func(c *fiber.Ctx) error {
		start := time.Now()
		err := c.Next()
		duration := time.Since(start)
 
		log.Printf("%s %s %d %s",
			c.Method(),
			c.Path(),
			c.Response().StatusCode(),
			duration,
		)
 
		return err
	})
}

الخطوة 7: ربط كل شيء معاً

أنشئ نقطة الدخول في cmd/api/main.go:

package main
 
import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
 
	"github.com/gofiber/fiber/v2"
	"github.com/yourusername/task-api/internal/config"
	"github.com/yourusername/task-api/internal/database"
	"github.com/yourusername/task-api/internal/handlers"
	"github.com/yourusername/task-api/internal/middleware"
)
 
func main() {
	// تحميل الإعدادات
	cfg := config.LoadConfig()
 
	// الاتصال بقاعدة البيانات
	database.Connect(cfg)
 
	// إنشاء تطبيق Fiber
	app := fiber.New(fiber.Config{
		AppName:      "Task API v1.0",
		ErrorHandler: customErrorHandler,
	})
 
	// إعداد الوسطاء
	middleware.SetupMiddleware(app)
 
	// فحص الصحة
	app.Get("/health", func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{
			"status":  "ok",
			"service": "task-api",
		})
	})
 
	// مسارات API v1
	v1 := app.Group("/api/v1")
	{
		tasks := v1.Group("/tasks")
		tasks.Get("/", handlers.ListTasks)
		tasks.Get("/:id", handlers.GetTask)
		tasks.Post("/", handlers.CreateTask)
		tasks.Put("/:id", handlers.UpdateTask)
		tasks.Delete("/:id", handlers.DeleteTask)
	}
 
	// الإيقاف الآمن
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 
	go func() {
		<-quit
		log.Println("Shutting down server...")
		if err := app.Shutdown(); err != nil {
			log.Fatalf("Server shutdown failed: %v", err)
		}
	}()
 
	// تشغيل الخادم
	addr := fmt.Sprintf(":%s", cfg.AppPort)
	log.Printf("Server starting on %s", addr)
	log.Fatal(app.Listen(addr))
}
 
func customErrorHandler(c *fiber.Ctx, err error) error {
	code := fiber.StatusInternalServerError
	if e, ok := err.(*fiber.Error); ok {
		code = e.Code
	}
	return c.Status(code).JSON(fiber.Map{
		"error": err.Error(),
	})
}

الخطوة 8: تشغيل واختبار الـ API

شغّل الخادم:

go run cmd/api/main.go

سترى:

Database connected successfully
Database migrated successfully
Server starting on :3000

الآن اختبر باستخدام curl:

إنشاء مهمة:

curl -X POST http://localhost:3000/api/v1/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "تعلّم Go Fiber",
    "description": "إكمال درس REST API",
    "priority": 3
  }'

الاستجابة:

{
  "id": 1,
  "title": "تعلّم Go Fiber",
  "description": "إكمال درس REST API",
  "status": "pending",
  "priority": 3,
  "created_at": "2026-03-02T10:00:00Z",
  "updated_at": "2026-03-02T10:00:00Z"
}

عرض جميع المهام:

curl http://localhost:3000/api/v1/tasks

تحديث مهمة:

curl -X PUT http://localhost:3000/api/v1/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"status": "in_progress"}'

التصفية حسب الحالة:

curl "http://localhost:3000/api/v1/tasks?status=pending&page=1&limit=5"

حذف مهمة:

curl -X DELETE http://localhost:3000/api/v1/tasks/1

الخطوة 9: كتابة اختبارات التكامل

أنشئ cmd/api/main_test.go:

package main
 
import (
	"bytes"
	"encoding/json"
	"net/http/httptest"
	"testing"
 
	"github.com/gofiber/fiber/v2"
	"github.com/yourusername/task-api/internal/handlers"
	"github.com/yourusername/task-api/internal/models"
)
 
func setupTestApp() *fiber.App {
	app := fiber.New()
 
	v1 := app.Group("/api/v1")
	tasks := v1.Group("/tasks")
	tasks.Get("/", handlers.ListTasks)
	tasks.Get("/:id", handlers.GetTask)
	tasks.Post("/", handlers.CreateTask)
	tasks.Put("/:id", handlers.UpdateTask)
	tasks.Delete("/:id", handlers.DeleteTask)
 
	return app
}
 
func TestCreateTask(t *testing.T) {
	app := setupTestApp()
 
	task := models.CreateTaskRequest{
		Title:       "Test Task",
		Description: "A task for testing",
		Priority:    2,
	}
 
	body, _ := json.Marshal(task)
	req := httptest.NewRequest("POST", "/api/v1/tasks", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
 
	resp, err := app.Test(req, -1)
	if err != nil {
		t.Fatalf("Failed to make request: %v", err)
	}
 
	if resp.StatusCode != fiber.StatusCreated {
		t.Errorf("Expected status 201, got %d", resp.StatusCode)
	}
}
 
func TestCreateTaskValidation(t *testing.T) {
	app := setupTestApp()
 
	task := models.CreateTaskRequest{
		Title: "",
	}
 
	body, _ := json.Marshal(task)
	req := httptest.NewRequest("POST", "/api/v1/tasks", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
 
	resp, err := app.Test(req, -1)
	if err != nil {
		t.Fatalf("Failed to make request: %v", err)
	}
 
	if resp.StatusCode != fiber.StatusUnprocessableEntity {
		t.Errorf("Expected status 422, got %d", resp.StatusCode)
	}
}
 
func TestGetNonExistentTask(t *testing.T) {
	app := setupTestApp()
 
	req := httptest.NewRequest("GET", "/api/v1/tasks/99999", nil)
 
	resp, err := app.Test(req, -1)
	if err != nil {
		t.Fatalf("Failed to make request: %v", err)
	}
 
	if resp.StatusCode != fiber.StatusNotFound {
		t.Errorf("Expected status 404, got %d", resp.StatusCode)
	}
}

شغّل الاختبارات:

go test ./cmd/api/ -v

حل المشاكل الشائعة

خطأ "connection refused" عند الاتصال بـ PostgreSQL: تأكد من أن PostgreSQL تعمل وأن بيانات الاعتماد في .env صحيحة. إذا كنت تستخدم Docker:

docker run --name taskdb -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:15
docker exec -it taskdb psql -U postgres -c "CREATE DATABASE taskapi;"

أخطاء "go: module not found": شغّل go mod tidy لحل مشاكل الاعتماديات.

المنفذ مستخدم بالفعل: غيّر APP_PORT في .env أو أوقف العملية التي تستخدم المنفذ:

lsof -ti:3000 | xargs kill

مقارنة الأداء

إليك كيف يقارن Fiber مع الأُطر الشائعة عبر اللغات المختلفة:

الإطاراللغةطلب/ثانية (hello world)الذاكرة
FiberGo~500,000~10 MB
GinGo~350,000~12 MB
ExpressNode.js~40,000~80 MB
FastAPIPython~25,000~50 MB
AxumRust~550,000~8 MB

تختلف المعايير حسب العتاد. المصدر: TechEmpower Framework Benchmarks.


الخطوات التالية

  • إضافة مصادقة JWT باستخدام github.com/gofiber/jwt/v3
  • إضافة توثيق Swagger مع github.com/gofiber/swagger
  • النشر باستخدام Docker — إنشاء Dockerfile متعدد المراحل لحجم صورة أصغر
  • إضافة دعم WebSocket لتحديثات المهام الفورية
  • تنفيذ التخزين المؤقت مع Redis باستخدام github.com/gofiber/storage/redis

الخلاصة

لقد بنيت واجهة REST API متكاملة باستخدام Go و Fiber تتضمّن:

  • هيكل مشروع نظيف يتبع أفضل ممارسات Go
  • عمليات CRUD كاملة مع PostgreSQL و GORM
  • التحقق من المدخلات ومعالجة أخطاء منظّمة
  • وسطاء لـ CORS وتحديد المعدّل والتسجيل
  • إيقاف آمن للجاهزية الإنتاجية
  • اختبارات تكامل باستخدام أدوات الاختبار المدمجة في Fiber

Go و Fiber يمنحانك أداء لغة مُترجَمة مع تجربة تطوير شبيهة بـ Express.js. الملف التنفيذي الناتج هو ملف واحد مستقل يبدأ في أجزاء من الثانية ويتعامل مع آلاف الاتصالات المتزامنة بأقل استهلاك للذاكرة.

لمشروعك القادم، فكّر في إضافة المصادقة، دعم WebSocket، أو النشر على منصة سحابية باستخدام Docker. الأساس الذي بنيته هنا يتوسّع من المشاريع الجانبية إلى أحمال العمل الإنتاجية التي تخدم ملايين الطلبات.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على Better Auth مع Next.js 15: الدليل الشامل للمصادقة في 2026.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

بناء واجهات REST API باستخدام Rust و Axum: دليل عملي للمبتدئين

تعلّم كيف تبني واجهات REST API سريعة وآمنة باستخدام لغة Rust وإطار Axum. يغطي هذا الدليل خطوة بخطوة إعداد المشروع، التوجيه، معالجة JSON، الربط بقاعدة البيانات عبر SQLx، معالجة الأخطاء، والاختبارات — من الصفر إلى واجهة API عاملة.

30 د قراءة·