1- نظرة عامة
Cloud Run هي منصة مُدارة بالكامل تتيح لك تشغيل الرموز البرمجية مباشرةً على بنية Google الأساسية القابلة للتوسّع. سيوضّح هذا الدرس التطبيقي حول الترميز كيفية ربط تطبيق Next.js على Cloud Run بقاعدة بيانات Cloud SQL لـ PostgreSQL.
في هذا التمرين المعملي، ستتعرّف على كيفية:
- إنشاء مثيل Cloud SQL for PostgreSQL
- نشر تطبيق على Cloud Run يتصل بقاعدة بيانات Cloud SQL
2. المتطلبات الأساسية
- إذا لم يكن لديك حساب على Google، عليك إنشاء حساب على Google.
- استخدام حساب شخصي بدلاً من حساب عمل أو حساب تديره مؤسسة تعليمية قد تتضمّن حسابات العمل والمؤسسة التعليمية قيودًا تمنعك من تفعيل واجهات برمجة التطبيقات اللازمة لهذا الدرس التطبيقي.
3. إعداد المشروع
- سجِّل الدخول إلى Google Cloud Console.
- فعِّل الفوترة في Cloud Console.
- يجب أن تكلّف إكمال هذا المختبر أقل من دولار أمريكي واحد من موارد السحابة الإلكترونية.
- يمكنك اتّباع الخطوات في نهاية هذا المختبر لحذف الموارد وتجنُّب المزيد من الرسوم.
- يكون المستخدمون الجدد مؤهّلين للاستفادة من الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.
- أنشئ مشروعًا جديدًا أو اختَر إعادة استخدام مشروع حالي.
4. فتح Cloud Shell Editor
- الانتقال إلى محرّر Cloud Shell
- إذا لم تظهر النافذة الطرفية في أسفل الشاشة، افتحها باتّباع الخطوات التالية:
- انقر على قائمة الهامبرغر

- انقر على Terminal.
- انقر على نافذة طرفية جديدة
.
- انقر على قائمة الهامبرغر
- في الوحدة الطرفية، اضبط مشروعك باستخدام الأمر التالي:
- التنسيق:
gcloud config set project [PROJECT_ID] - مثال:
gcloud config set project lab-project-id-example - إذا تعذّر عليك تذكُّر رقم تعريف مشروعك، اتّبِع الخطوات التالية:
- يمكنك إدراج جميع أرقام تعريف المشاريع باستخدام:
gcloud projects list | awk '/PROJECT_ID/{print $2}'

- يمكنك إدراج جميع أرقام تعريف المشاريع باستخدام:
- التنسيق:
- إذا طُلب منك منح الإذن، انقر على منح الإذن للمتابعة.

- من المفترض أن تظهر لك هذه الرسالة:
إذا ظهرت لكUpdated property [core/project].
WARNINGوطُلب منكDo you want to continue (Y/N)?، من المحتمل أنّك أدخلت رقم تعريف المشروع بشكل غير صحيح. اضغط علىN، ثم علىEnter، وحاوِل تنفيذ الأمرgcloud config set projectمرة أخرى.
5- تفعيل واجهات برمجة التطبيقات
في الوحدة الطرفية، فعِّل واجهات برمجة التطبيقات:
gcloud services enable \
compute.googleapis.com \
sqladmin.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
networkconnectivity.googleapis.com \
servicenetworking.googleapis.com \
cloudaicompanion.googleapis.com
إذا طُلب منك منح الإذن، انقر على منح الإذن للمتابعة. 
قد يستغرق تنفيذ هذا الأمر بضع دقائق، ولكن من المفترض أن يعرض في النهاية رسالة نجاح مشابهة لما يلي:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. إعداد حساب خدمة
أنشئ حساب خدمة على Google Cloud وأعدّ إعداداته ليتم استخدامه من قِبل Cloud Run، وذلك لضمان حصوله على الأذونات الصحيحة للاتصال بخدمة Cloud SQL.
- نفِّذ الأمر
gcloud iam service-accounts createعلى النحو التالي لإنشاء حساب خدمة جديد:gcloud iam service-accounts create quickstart-service-account \ --display-name="Quickstart Service Account" - نفِّذ الأمر gcloud projects add-iam-policy-binding على النحو التالي لإضافة دور كاتب السجلّ إلى حساب خدمة Google Cloud الذي أنشأته للتو.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --role="roles/logging.logWriter"
7. إنشاء قاعدة بيانات Cloud SQL
- إنشاء "سياسة ربط الخدمة" للسماح بالاتصال بالشبكة من Cloud Run إلى Cloud SQL باستخدام Private Service Connect
gcloud network-connectivity service-connection-policies create quickstart-policy \ --network=default \ --project=${GOOGLE_CLOUD_PROJECT} \ --region=us-central1 \ --service-class=google-cloud-sql \ --subnets=https://www.googleapis.com/compute/v1/projects/${GOOGLE_CLOUD_PROJECT}/regions/us-central1/subnetworks/default - إنشاء كلمة مرور فريدة لقاعدة البيانات
export DB_PASSWORD=$(openssl rand -base64 20) - تشغيل الأمر
gcloud sql instances createلإنشاء مثيل Cloud SQLgcloud sql instances create quickstart-instance \ --project=${GOOGLE_CLOUD_PROJECT} \ --root-password=${DB_PASSWORD} \ --database-version=POSTGRES_17 \ --tier=db-perf-optimized-N-2 \ --region=us-central1 \ --ssl-mode=ENCRYPTED_ONLY \ --no-assign-ip \ --enable-private-service-connect \ --psc-auto-connections=network=projects/${GOOGLE_CLOUD_PROJECT}/global/networks/default
قد يستغرق إكمال هذا الأمر بضع دقائق.
- نفِّذ الأمر
gcloud sql databases createلإنشاء قاعدة بيانات Cloud SQL ضِمنquickstart-instance.gcloud sql databases create quickstart_db \ --instance=quickstart-instance
8. تجهيز التطبيق
إعداد تطبيق Next.js يستجيب لطلبات HTTP
- لإنشاء مشروع Next.js جديد باسم
task-app، استخدِم الأمر:npx --yes create-next-app@15 task-app \ --ts \ --eslint \ --tailwind \ --no-src-dir \ --turbopack \ --app \ --no-import-alias - غيِّر الدليل إلى
task-app:cd task-app - ثبِّت
pgللتفاعل مع قاعدة بيانات PostgreSQL.npm install pg - ثبِّت
@types/pgكحزمة تطوير لاستخدام تطبيق Next.js المستند إلى TypeScript.npm install --save-dev @types/pg - افتح الملف
actions.tsفي "محرِّر Cloud Shell": من المفترض أن يظهر ملف فارغ الآن في الجزء العلوي من الشاشة. هذا هو المكان الذي يمكنك فيه تعديل ملفcloudshell edit app/actions.tsactions.tsهذا.
- انسخ الرمز التالي والصِقه في ملف
actions.tsالذي تم فتحه:'use server' import pg from 'pg'; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; const { Pool } = pg; const pool = new Pool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, ssl: { // @ts-expect-error require true is not recognized by @types/pg, but does exist on pg require: true, rejectUnauthorized: false, // required for self-signed certs // https://node-postgres.com/features/ssl#self-signed-cert } }); const tableCreationIfDoesNotExist = async () => { await pool.query(`CREATE TABLE IF NOT EXISTS tasks ( id SERIAL NOT NULL, created_at timestamp NOT NULL, status VARCHAR(255) NOT NULL default 'IN_PROGRESS', title VARCHAR(1024) NOT NULL, PRIMARY KEY (id) );`); } // CREATE export async function addNewTaskToDatabase(newTask: string) { await tableCreationIfDoesNotExist(); await pool.query(`INSERT INTO tasks(created_at, status, title) VALUES(NOW(), 'IN_PROGRESS', $1)`, [newTask]); return; } // READ export async function getTasksFromDatabase() { await tableCreationIfDoesNotExist(); const { rows } = await pool.query(`SELECT id, created_at, status, title FROM tasks ORDER BY created_at DESC LIMIT 100`); return rows; } // UPDATE export async function updateTaskInDatabase(task: Task) { await tableCreationIfDoesNotExist(); await pool.query( `UPDATE tasks SET status = $1, title = $2 WHERE id = $3`, [task.status, task.title, task.id] ); return; } // DELETE export async function deleteTaskFromDatabase(taskId: string) { await tableCreationIfDoesNotExist(); await pool.query(`DELETE FROM tasks WHERE id = $1`, [taskId]); return; } - افتح الملف
page.tsxفي "محرِّر Cloud Shell": من المفترض أن يظهر ملف حالي في الجزء العلوي من الشاشة. هذا هو المكان الذي يمكنك فيه تعديل ملفcloudshell edit app/page.tsxpage.tsxهذا.
- احذف المحتوى الحالي لملف
page.tsx. - انسخ الرمز التالي والصِقه في ملف
page.tsxالذي تم فتحه:'use client' import React, { useEffect, useState } from "react"; import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions"; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; export default function Home() { const [newTaskTitle, setNewTaskTitle] = useState(''); const [tasks, setTasks] = useState<Task[]>([]); async function getTasks() { const updatedListOfTasks = await getTasksFromDatabase(); setTasks(updatedListOfTasks); } useEffect(() => { getTasks(); }, []); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); await addNewTaskToDatabase(newTaskTitle); await getTasks(); setNewTaskTitle(''); }; async function updateTask(task: Task, newTaskValues: Partial<Task>) { await updateTaskInDatabase({ ...task, ...newTaskValues }); await getTasks(); } async function deleteTask(taskId: string) { await deleteTaskFromDatabase(taskId); await getTasks(); } return ( <main className="p-4"> <h2 className="text-2xl font-bold mb-4">To Do List</h2> <div className="flex mb-4"> <form onSubmit={handleSubmit} className="flex mb-8"> <input type="text" placeholder="New Task Title" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit" /> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap" > Add New Task </button> </form> </div> <table className="w-full"> <tbody> {tasks.map(function (task) { const isComplete = task.status === 'COMPLETE'; return ( <tr key={task.id} className="border-b border-gray-200"> <td className="py-2 px-4"> <input type="checkbox" checked={isComplete} onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })} className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500" /> </td> <td className="py-2 px-4"> <span className={`transition-all duration-300 ease-in-out ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`} > {task.title} </span> </td> <td className="py-2 px-4"> <button onClick={() => deleteTask(task.id)} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right" > Delete </button> </td> </tr> ); })} </tbody> </table> </main> ); }
أصبح التطبيق جاهزًا الآن للنشر.
9- نشر التطبيق على Cloud Run
- نفِّذ الأمر gcloud projects add-iam-policy-binding على النحو التالي لإضافة دور مستخدم الشبكة إلى حساب خدمة Cloud Run لخدمة Cloud Run التي ستنشئها.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member "serviceAccount:service-$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} --format="value(projectNumber)")@serverless-robot-prod.iam.gserviceaccount.com" \ --role "roles/compute.networkUser"
- نفِّذ الأمر gcloud projects add-iam-policy-binding على النحو التالي لإضافة دور كاتب Artifact Registry إلى المستخدم الحالي لخدمة Cloud Run التي ستنشئها.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud auth list --filter=status:ACTIVE --format="value(account)") \ --role="roles/artifactregistry.writer"
- نفِّذ الأمر أدناه لنشر تطبيقك على Cloud Run:
gcloud run deploy helloworld \ --region=us-central1 \ --source=. \ --set-env-vars DB_NAME="quickstart_db" \ --set-env-vars DB_USER="postgres" \ --set-env-vars DB_PASSWORD=${DB_PASSWORD} \ --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \ --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --network=default \ --subnet=default \ --allow-unauthenticated - إذا طُلب منك ذلك، اضغط على
YوEnterلتأكيد رغبتك في المتابعة:Do you want to continue (Y/n)? Y
بعد بضع دقائق، من المفترض أن يقدّم لك التطبيق عنوان URL للانتقال إليه.
انتقِل إلى عنوان URL للاطّلاع على تطبيقك أثناء عمله. في كل مرة تزور فيها عنوان URL أو تعيد تحميل الصفحة، سيظهر لك تطبيق المهام.
10. إضافة ميزة باستخدام Gemini Code Assist
لقد نشرت الآن تطبيق ويب يتضمّن قاعدة بيانات. بعد ذلك، سنضيف ميزة جديدة إلى تطبيق next.js باستخدام قوة المساعدة المستندة إلى الذكاء الاصطناعي.
- الرجوع إلى Cloud Shell Editor
- فتح
page.tsxمرة أخرىcd ~/task-app cloudshell edit app/page.tsx - انتقِل إلى Gemini Code Assist في Cloud Shell Editor:
- انقر على رمز Gemini
في شريط الأدوات على يمين الشاشة - سجِّل الدخول باستخدام بيانات اعتماد حسابك على Google عندما يُطلب منك ذلك.
- إذا طُلب منك اختيار مشروع، اختَر المشروع الذي أنشأته لهذا الدرس العملي

- انقر على رمز Gemini
- أدخِل الطلب:
Add the ability to update the title of the task. The code in your output should be complete and working code.. يجب أن يتضمّن الرد مقتطفات مشابهة لما يلي لإضافة الدالتَينhandleEditStartوhandleEditCancel:const [editingTaskId, setEditingTaskId] = useState(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; - استبدِل
page.tsxبنتيجة Gemini Code Assist. في ما يلي مثال عملي:'use client' import React, { useEffect, useState } from "react"; import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions"; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; export default function Home() { const [newTaskTitle, setNewTaskTitle] = useState(''); const [tasks, setTasks] = useState<Task[]>([]); const [editingTaskId, setEditingTaskId] = useState(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); async function getTasks() { const updatedListOfTasks = await getTasksFromDatabase(); setTasks(updatedListOfTasks); } useEffect(() => { getTasks(); }, []); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); await addNewTaskToDatabase(newTaskTitle); await getTasks(); setNewTaskTitle(''); }; async function updateTask(task: Task, newTaskValues: Partial<Task>) { await updateTaskInDatabase({ ...task, ...newTaskValues }); await getTasks(); setEditingTaskId(''); setEditedTaskTitle(''); } async function deleteTask(taskId: string) { await deleteTaskFromDatabase(taskId); await getTasks(); } function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; return ( <main className="p-4"> <h2 className="text-2xl font-bold mb-4">To Do List</h2> <div className="flex mb-4"> <form onSubmit={handleSubmit} className="flex mb-8"> <input type="text" placeholder="New Task Title" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit" /> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap" > Add New Task </button> </form> </div> <table className="w-full"> <tbody> {tasks.map(function (task) { const isComplete = task.status === 'COMPLETE'; return ( <tr key={task.id} className="border-b border-gray-200"> <td className="py-2 px-4"> <input type="checkbox" checked={isComplete} onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })} className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500" /> </td> <td className="py-2 px-4"> {editingTaskId === task.id ? ( <form onSubmit={(e) => { e.preventDefault(); updateTask(task, { title: editedTaskTitle }); }} className="flex" > <input type="text" value={editedTaskTitle} onChange={(e) => setEditedTaskTitle(e.target.value)} onBlur={() => updateTask(task, { title: editedTaskTitle })} // Handle clicking outside input className="flex-grow border border-gray-400 rounded px-3 py-1 mr-2 bg-inherit" /> </form> ) : ( <span onClick={() => handleEditStart(task)} className={`transition-all duration-300 ease-in-out cursor-pointer ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`} > {task.title} </span> )} </td> <td className="py-2 px-4"> <button onClick={() => deleteTask(task.id)} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right" > Delete </button> </td> </tr> ); })} </tbody> </table> </main> ); }
11. إعادة نشر التطبيق على Cloud Run
- نفِّذ الأمر أدناه لنشر تطبيقك على Cloud Run:
gcloud run deploy helloworld \ --region=us-central1 \ --source=. \ --set-env-vars DB_NAME="quickstart_db" \ --set-env-vars DB_USER="postgres" \ --set-env-vars DB_PASSWORD=${DB_PASSWORD} \ --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \ --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --network=default \ --subnet=default \ --allow-unauthenticated - إذا طُلب منك ذلك، اضغط على
YوEnterلتأكيد رغبتك في المتابعة:Do you want to continue (Y/n)? Y
12. تهانينا
في هذه الميزة الاختبارية، تعرّفت على كيفية تنفيذ ما يلي:
- إنشاء مثيل Cloud SQL for PostgreSQL
- نشر تطبيق على Cloud Run يتصل بقاعدة بيانات Cloud SQL
تَنظيم
لا تتوفّر طبقة مجانية في Cloud SQL، وسيتم تحصيل رسوم منك إذا واصلت استخدامها. يمكنك حذف مشروعك على Google Cloud لتجنُّب تحمّل رسوم إضافية.
على الرغم من أنّ Cloud Run لا يفرض رسومًا عندما لا تكون الخدمة قيد الاستخدام، قد يتم تحصيل رسوم منك مقابل تخزين صورة الحاوية في Artifact Registry. يؤدي حذف مشروعك على السحابة الإلكترونية إلى إيقاف الفوترة لجميع الموارد المستخدَمة في هذا المشروع.
إذا أردت، يمكنك حذف المشروع باتّباع الخطوات التالية:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
يمكنك أيضًا حذف الموارد غير الضرورية من قرص cloudshell. يمكنك إجراء ما يلي:
- احذف دليل مشروع الدرس البرمجي:
rm -rf ~/task-app - تحذير! لا يمكن التراجع عن الإجراء التالي. إذا أردت حذف كل شيء على Cloud Shell لإخلاء بعض المساحة، يمكنك حذف دليل منزلك بأكمله. يجب الحرص على حفظ كل ما تريد الاحتفاظ به في مكان آخر.
sudo rm -rf $HOME
مواصلة التعلّم
- نشر تطبيق Next.js كامل الميزات على Cloud Run باستخدام Cloud SQL لـ PostgreSQL من خلال Cloud SQL Node.js Connector
- نشر تطبيق Angular كامل الميزات على Cloud Run باستخدام Cloud SQL for PostgreSQL من خلال Cloud SQL Node.js Connector
- نشر تطبيق Angular متكامل على Cloud Run باستخدام Firestore من خلال حزمة تطوير البرامج (SDK) للمشرف في Node.js
- نشر تطبيق Next.js متكامل على Cloud Run باستخدام Firestore من خلال Node.js Admin SDK