بناء تطبيق ويب متكامل: دليل عملي لربط Go و Vue.js
Go، وجدت صعوبة في استيعاب مفاهيمها الأولية. كانت ذات مستوى أدنى بكثير (low-level) من أي لغة أخرى تعاملت معها سابقًا. ولكن بعد بضعة أشهر، أصبحت من أشد المعجبين بها وأستخدمها في العديد من المشاريع. في هذا المقال، سأشارككم كيفية إعداد تطبيق ويب متكامل (full-stack) يجمع بين قوة Go في الواجهة الخلفية ومرونة Vue.js في الواجهة الأمامية. لننطلق في هذا الدليل العملي!
ماذا سنبني في هذا المشروع؟
تتمثل فكرتنا في بناء أداة لإنشاء صور مصغرة (thumbnails) للمواقع الإلكترونية. ببساطة، سيقوم المستخدم بإدخال عنوان URL لموقع معين، وسيقوم التطبيق بتوليد صورة مصغرة لذلك الموقع.
إعداد وحدة Go (Go Module)
الخطوة الأولى هي إنشاء دليل جديد للمشروع. بعد ذلك، نقوم بتهيئة وحدة Go عن طريق تنفيذ الأمر التالي في سطر الأوامر:
go mod init github.com/Dirk94/website-thumbnail-generator
سينتج عن هذا الأمر إنشاء ملف go.mod، وهو المسؤول عن تتبع جميع تبعيات الوحدة (module dependencies). يمكن تشبيهه بملف package.json في مشاريع Node.js.
بعد ذلك، سنقوم بإنشاء دليل فرعي جديد باسم main، وداخله سنضيف ملفًا باسم server.go. سيكون هذا الملف هو نقطة الدخول الرئيسية لتطبيقنا. في الوقت الحالي، لنكتفِ بطباعة رسالة “Hello world” بسيطة:
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
لتشغيل هذا البرنامج، ننفذ الأمر التالي من دليل المشروع الرئيسي:
go run main/server.go
يجب أن يظهر الإخراج التالي:
Hello world
رائع! حتى الآن، كل شيء يعمل بسلاسة.
إعداد خادم الويب (Web Server)
نحتاج الآن إلى إنشاء خادم ويب يستمع للطلبات الواردة. لنقم بتحديث الدالة main لتشمل هذه الوظيفة:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", homePageHandler)
fmt.Println("Server listening on port 3000")
log.Panic(http.ListenAndServe(":3000", nil))
}
func homePageHandler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "hello world")
checkError(err)
}
func checkError(err error) {
if err != nil {
log.Panic(err)
}
}
يقوم هذا التحديث بتشغيل خادم ويب يستمع على المنفذ 3000. أي طلب وارد سيتم معالجته بواسطة الدالة homePageHandler. بما أن هذه الدالة لم تكن موجودة بعد، فقد قمنا بإنشائها.
وظيفة الدالة homePageHandler بسيطة للغاية؛ فهي تكتب رسالة “hello world” إلى كائن http.ResponseWriter. أما الدالة checkError فهي دالة مساعدة مفيدة توقف البرنامج وتطبع تتبع المكدس (stack trace) في حال كان هناك خطأ (أي عندما تكون قيمة error ليست nil).
عند تشغيل البرنامج الآن، سيقوم خادم الويب بطباعة رسالة “hello world” بشكل صحيح عند زيارة localhost:3000!

إنشاء مشروع Vue.js
لإنشاء مشروع Vue.js جديد، نقوم بتشغيل الأمر التالي من دليل المشروع الرئيسي:
vue create frontend
سيؤدي هذا الأمر إلى إنشاء عدد كبير من الملفات، ولكن لا داعي للقلق. لنبدأ بتشغيل خادم التطوير الخاص بـ Vue:
yarn serve
عند التوجه إلى localhost:8081 في متصفحك، ستلاحظ أن تطبيق Vue يعمل بشكل صحيح!

والآن، دعونا ننظف دليل الواجهة الأمامية frontend قليلاً. في البداية، سنحذف دليلي assets و components نظرًا لأننا لن نستخدمهما في هذا المشروع. بعد ذلك، سنقوم بتحديث ملف App.vue ليحتوي على الهيكل الأساسي لتطبيقنا:
<template>
<div id="app" class="container">
<div class="row">
<div class="col-md-6 offset-md-3 py-5">
<h1>إنشاء صورة مصغرة لموقع إلكتروني</h1>
<form v-on:submit.prevent="makeWebsiteThumbnail">
<div class="form-group">
<input
v-model="websiteUrl"
type="text"
id="website-input"
placeholder="أدخل عنوان الموقع هنا"
class="form-control"
/>
</div>
<div class="form-group">
<button class="btn btn-primary">إنشاء!</button>
</div>
</form>
</div>
</div>
</div>
</template>
في هذا الكود، استخدمنا التوجيه v-model لربط حقل الإدخال بالبيانات، وقمنا باستدعاء الدالة makeWebsiteThumbnail عند إرسال النموذج. حاليًا، هذه الدالة غير موجودة، لذا دعنا نضيفها إلى قسم <script> في ملف App.vue:
<script>
export default {
name: 'App',
data() {
return {
websiteUrl: '',
};
},
methods: {
makeWebsiteThumbnail() {
console.log(`يجب أن أقوم بإنشاء صورة مصغرة للموقع: ${this.websiteUrl}`);
},
},
};
</script>
نحن نستخدم أيضًا بعض فئات Bootstrap 4 لتنسيق الواجهة، ولجعلها تعمل بشكل صحيح، يجب إضافة ملف CSS الخاص بـ Bootstrap إلى ملف public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous"
/>
<!--- باقي المحتوى في وسم head هنا... --->
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
الآن، لنعد تشغيل خادم التطوير ونرى رسالة السجل في وحدة التحكم (console) عند إرسال النموذج.

ممتاز، لقد نجح الأمر!
إنشاء الصورة المصغرة للموقع
لإنشاء الصورة المصغرة للموقع، سنستعين بخدمة screenshotapi.net. بهذه الطريقة، لن نحتاج سوى لاستدعاء واجهة برمجة تطبيقات (API) لتتولى عنا المهمة الثقيلة.
أولاً، نقوم بتثبيت مكتبة axios لإجراء طلبات HTTP:
yarn add axios
ثم نقوم باستيرادها في ملف App.vue:
<script>
import axios from 'axios';
export default {
name: 'App',
// باقي الكود هنا...
</script>
بعد ذلك، نقوم بتحديث الدالة makeWebsiteThumbnail لتستدعي واجهة برمجة تطبيقات لقطة الشاشة (screenshot API) بالفعل:
makeWebsiteThumbnail() {
axios.post("https://screenshotapi.net/api/v1/screenshot", {
token: "SCREENSHOTAPI_TOKEN", // استبدل هذا برمز API الخاص بك
url: this.websiteUrl,
width: 1920,
height: 1080,
output: 'json',
thumbnail_width: 300
})
.then((response) => {
this.thumbnailUrl = response.data.screenshot;
})
.catch((error) => {
window.alert(`أعادت واجهة برمجة التطبيقات خطأ: ${error}`);
});
}
**ملاحظة هامة**: تأكد من استبدال SCREENSHOTAPI_TOKEN بالرمز الخاص بك من screenshotapi.net.
نقوم بتعيين المتغير thumbnailUrl ليكون عنوان URL للصورة المصغرة التي تم إنشاؤها بواسطة واجهة برمجة التطبيقات. لجعل هذا يعمل، يجب إضافة أمرين:
- إضافة المتغير
thumbnailUrlإلى كائنdataفيVue:data: { websiteUrl: '', thumbnailUrl: '', }, - إنشاء وسم
<img>لعرض الصورة المصغرة باستخدامthumbnailUrl:<img :src="thumbnailUrl" alt="صورة مصغرة للموقع" />
لنقم بتشغيل خادم الويب ونرى النتيجة:

رائع! لقد عرضت الصورة المصغرة لموقع freeCodeCamp بنجاح.
ربط Go و Vue معًا
حتى الآن، استخدمنا خادم تطوير Vue لتشغيل الواجهة الأمامية. يعمل هذا بشكل جيد، ولكن خادم التطوير مخصص فقط للاستخدام في بيئة التطوير المحلية. عندما نستضيف هذا التطبيق في بيئة إنتاج، سنحتاج إلى خادم ويب “حقيقي” للتعامل مع الطلبات الواردة. لحسن الحظ، لدينا بالفعل خادم Go الذي قمنا بإعداده.
الخطوة الأولى هي تجميع (compile) الواجهة الأمامية لتطبيق Vue:
yarn run build
سيؤدي هذا الأمر إلى إنشاء دليل باسم dist يحتوي على الأصول المترجمة (compiled assets) لتطبيق Vue.
الآن، يجب علينا تحديث خادم Go لتقديم الملفات من هذا الدليل. للقيام بذلك، سنقوم بتعديل الدالة main في ملف main.go:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// تقديم الملفات الثابتة من دليل frontend/dist.
fs := http.FileServer(http.Dir("./frontend/dist"))
http.Handle("/", fs)
// بدء تشغيل الخادم.
fmt.Println("Server listening on port 3000")
log.Panic(http.ListenAndServe(":3000", nil))
}
// الدالتان homePageHandler و checkError من الخطوات السابقة يمكن إزالتهما أو تعديلهما حسب الحاجة
// أو يمكن الاحتفاظ بهما إذا كانت هناك مسارات أخرى غير المسار الجذري
// في هذا السياق، يمكن إزالة homePageHandler إذا كان المسار الجذري سيخدم ملفات Vue فقط.
// للحفاظ على السياق، سنفترض أننا نستخدمها فقط لتقديم الملفات الثابتة.
كما ترون، قمنا ببساطة بتمرير دليل frontend/dist إلى دالة http.FileServer. عند تشغيل برنامج Go والتنقل إلى localhost:3000، ستتمكن بالفعل من رؤية التطبيق يعمل بشكل متكامل!
تعزيز أمان التطبيق
في الوضع الحالي، لدينا ثغرة أمنية خطيرة: رمز API الخاص بخدمة لقطة الشاشة (screenshot API token) مرئي في كود الواجهة الأمامية لدينا. هذا يعني أن أي شخص يقوم بفحص صفحة الويب يمكنه سرقة هذا الرمز.
لإصلاح هذه المشكلة، سنقوم بجعل خادم Go هو المسؤول عن استدعاء واجهة برمجة تطبيقات لقطة الشاشة. بهذه الطريقة، لن يحتاج سوى الخادم لمعرفة الرمز السري.
في ملف server.go، سنقوم بإنشاء دالة جديدة تستمع لأي طلب وارد إلى نقطة النهاية /api/thumbnail:
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
)
// هيكل لفك تشفير طلب الصورة المصغرة من الواجهة الأمامية
type thumbnailRequest struct {
Url string `json:"url"`
}
// دالة معالج طلب الصورة المصغرة
func thumbnailHandler(w http.ResponseWriter, r *http.Request) {
var decoded thumbnailRequest
// محاولة فك تشفير الطلب إلى هيكل thumbnailRequest
err := json.NewDecoder(r.Body).Decode(&decoded)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Printf("تم استلام عنوان URL التالي: %s\n", decoded.Url)
}
func checkError(err error) {
if err != nil {
log.Panic(err)
}
}
func main() {
// استخدام دالة thumbnailHandler للمسار /api/thumbnail
http.HandleFunc("/api/thumbnail", thumbnailHandler)
// تقديم الملفات الثابتة من دليل frontend/dist.
fs := http.FileServer(http.Dir("./frontend/dist"))
http.Handle("/", fs)
fmt.Println("Server listening on port 3000")
log.Panic(http.ListenAndServe(":3000", nil))
}
في الوقت الحالي، تقوم هذه الدالة فقط باستخراج وطباعة معلمة URL من الطلب. لجعل هذا يعمل، سنقوم بتحديث الدالة main لاستخدام دالة thumbnailHandler الجديدة:
func main() {
// استخدام دالة thumbnailHandler
http.HandleFunc("/api/thumbnail", thumbnailHandler)
fs := http.FileServer(http.Dir("./frontend/dist"))
http.Handle("/", fs)
fmt.Println("Server listening on port 3000")
log.Panic(http.ListenAndServe(":3000", nil))
}
أخيرًا، يجب علينا تحديث ملف App.vue لاستدعاء خادم Go بدلاً من استدعاء واجهة برمجة تطبيقات لقطة الشاشة مباشرةً:
<script>
import axios from 'axios';
export default {
name: 'App',
data() {
return {
websiteUrl: '',
thumbnailUrl: '',
};
},
methods: {
makeWebsiteThumbnail() {
// استدعاء واجهة برمجة تطبيقات Go، في هذه الحالة نحتاج فقط لمعلمة URL.
axios.post("http://localhost:3000/api/thumbnail", {
url: this.websiteUrl,
})
.then((response) => {
// هنا سيتوقع التطبيق أن يستقبل response.data.screenshot من خادم Go
this.thumbnailUrl = response.data.screenshot;
})
.catch((error) => {
window.alert(`أعادت واجهة برمجة التطبيقات خطأ: ${error}`);
});
},
},
};
</script>
عند اختبار الإعداد الجديد، سنرى بالفعل رسالة سجل في خادم Go تشير إلى استلام عنوان URL:
go run main/server.go
تم استلام عنوان URL التالي: freecodecamp.org
استدعاء واجهة برمجة تطبيقات لقطة الشاشة من Go
الآن، لنقم بالفعل باستدعاء واجهة برمجة تطبيقات لقطة الشاشة (Screenshot API) من خادم Go الخاص بنا.
للبدء، سنقوم بإنشاء هيكل (struct) يحتوي على جميع المعلمات اللازمة لاستدعاء Screenshot API:
type screenshotAPIRequest struct {
Token string `json:"token"`
Url string `json:"url"`
Output string `json:"output"`
Width int `json:"width"`
Height int `json:"height"`
ThumbnailWidth int `json:"thumbnail_width"`
}
بعد ذلك، سنقوم بتحديث الدالة thumbnailHandler لإنشاء طلب HTTP POST واستدعاء واجهة برمجة التطبيقات:
func thumbnailHandler(w http.ResponseWriter, r *http.Request) {
var decoded thumbnailRequest
// محاولة فك تشفير الطلب الوارد من الواجهة الأمامية
err := json.NewDecoder(r.Body).Decode(&decoded)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// إنشاء هيكل بالمعلمات المطلوبة لاستدعاء ScreenshotAPI
apiRequest := screenshotAPIRequest{
Token: "SCREENSHOTAPI_TOKEN", // استبدل هذا برمز API الخاص بك
Url: decoded.Url,
Output: "json",
Width: 1920,
Height: 1080,
ThumbnailWidth: 300,
}
// تحويل الهيكل إلى سلسلة JSON
jsonString, err := json.Marshal(apiRequest)
checkError(err)
// إنشاء طلب HTTP
req, err := http.NewRequest("POST", "https://screenshotapi.net/api/v1/screenshot", bytes.NewBuffer(jsonString))
checkError(err)
req.Header.Set("Content-Type", "application/json")
// تنفيذ طلب HTTP
client := &http.Client{}
response, err := client.Do(req)
checkError(err)
// إخبار Go بإغلاق الاستجابة في نهاية الدالة
defer response.Body.Close()
// قراءة الاستجابة الخام إلى هيكل Go
type screenshotAPIResponse struct {
Screenshot string `json:"screenshot"`
}
var apiResponse screenshotAPIResponse
err = json.NewDecoder(response.Body).Decode(&apiResponse)
checkError(err)
// تمرير عنوان URL للصورة المصغرة إلى الواجهة الأمامية
_, err = fmt.Fprintf(w, `{ "screenshot": "%s" }`, apiResponse.Screenshot)
checkError(err)
}
عند إعادة تشغيل خادم Go، ستلاحظ أن مولد الصور المصغرة لا يزال يعمل بكفاءة! وكفائدة إضافية، لم يعد بإمكان أي شخص سرقة رمز API الخاص بنا الآن، لأنه مخزن على الخادم ولا يتعرض للواجهة الأمامية.

الخلاصة التقنية
في هذا الدليل الشامل، نجحنا في بناء تطبيق متكامل لإنشاء صور مصغرة للمواقع الإلكترونية، مستفيدين من قوة لغة Go في الواجهة الخلفية ومرونة إطار عمل Vue.js في الواجهة الأمامية. لقد قمنا بفصل واضح بين الواجهتين، ودمجنا واجهة برمجة تطبيقات خارجية (Screenshot API) التي يتم استدعاؤها بأمان من خلال خادم Go. هذا النهج لا يضمن فقط الأداء الجيد، بل يعزز أيضًا الأمان من خلال إخفاء رموز API الحساسة عن جانب العميل. يمثل هذا المشروع نموذجًا ممتازًا لكيفية بناء تطبيقات ويب حديثة وفعالة باستخدام مجموعة تقنيات قوية ومترابطة. يمكنكم استكشاف النسخة الحية للمشروع هنا والاطلاع على الكود المصدري على GitHub هنا. نتمنى لكم تجربة برمجة ممتعة ومثمرة!