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

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:
| الميزة | Fiber | Gin | Echo | Chi |
|---|---|---|---|---|
| مستوحى من | Express.js | Martini | Sinatra | المكتبة القياسية |
| محرك HTTP | Fasthttp | net/http | net/http | net/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) | الذاكرة |
|---|---|---|---|
| Fiber | Go | ~500,000 | ~10 MB |
| Gin | Go | ~350,000 | ~12 MB |
| Express | Node.js | ~40,000 | ~80 MB |
| FastAPI | Python | ~25,000 | ~50 MB |
| Axum | Rust | ~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. الأساس الذي بنيته هنا يتوسّع من المشاريع الجانبية إلى أحمال العمل الإنتاجية التي تخدم ملايين الطلبات.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء واجهات REST API جاهزة للإنتاج باستخدام FastAPI و PostgreSQL و Docker
تعلّم كيف تبني وتختبر وتنشر واجهة REST API احترافية باستخدام إطار FastAPI في بايثون مع PostgreSQL و SQLAlchemy و Alembic و Docker Compose — من الصفر حتى النشر.

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

بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج
تعلّم كيفية بناء تطبيق متكامل آمن الأنواع باستخدام Drizzle ORM مع Next.js 15. يغطي هذا الدليل العملي تصميم المخططات والترحيلات وServer Actions وعمليات CRUD والنشر مع PostgreSQL.