كيفية بناء تطبيق عديم الخوادم متكامل خلال أقل من ساعتين
مقدمة: لماذا نبني تطبيقاً عديم الخوادم؟
إذا كنت ترغب في التعمق في بناء تطبيقات Serverless الكاملة، فهذا الدليل العملي سيأخذك خطوة بخطوة لإنشاء مختصر روابط يعمل فعلياً ويمكن استخدامه ومشاركته مع الآخرين. سنعتمد على TypeScript وReact وNext.js وMongoDB، ثم ننشر التطبيق على Vercel للاستفادة من البنية عديمة الخوادم.
الفكرة هنا ليست إنشاء واجهة بسيطة وصفحتين فقط، بل بناء تطبيق متكامل يحفظ الروابط في قاعدة البيانات، ويعيد توجيه المستخدمين إلى الروابط الأصلية، ويمنحك أساساً ممتازاً لبناء مشاريع Serverless أخرى لاحقاً.

ماذا ستتعلم من هذا المشروع؟
- إعداد مشروع
Next.jsيدعمTypeScript. - بناء واجهة استخدام سريعة عبر مكتبة
Ant Design. - إنشاء واجهات
APIداخلية لمعالجة اختصار الروابط وإعادة التوجيه. - الاتصال بقاعدة بيانات
MongoDB Atlasوتخزين البيانات بشكل آمن. - نشر التطبيق على
Vercelمع إعدادات إعادة كتابة الروابط.
بحسب خبرتك السابقة، قد تحتاج إلى ساعة أو ساعتين لإكمال التطبيق. وإذا كنت تريد الوصول السريع إلى نتيجة عملية، فيمكنك تطبيق الخطوات مباشرة ثم تخصيص المشروع لاحقاً بما يناسب احتياجاتك.

المتطلبات الأساسية قبل البدء
لن تحتاج إلى خبرة متقدمة، لكن من المهم أن تكون لديك معرفة أساسية بـ Node.js، بالإضافة إلى فهم جيد لاستخدام Git وGitHub. لن نشرح هنا كيفية إنشاء مستودع جديد أو رفع الشيفرة إليه، لذلك يفضّل أن تكون مرتاحاً مع هذه الخطوات.
الخطوة الأولى: إنشاء مشروع Next.js
ابدأ بإنشاء مشروع جديد عبر الطرفية باستخدام الأمر التالي:
$ npx create-next-app yals
يمكنك تغيير اسم المشروع كما تريد. الاسم YALS هو اختصار لعبارة Yet Another Link Shortener.
إضافة دعم TypeScript
رغم أن Next.js يعمل جيداً مع JavaScript فقط، فإن استخدام TypeScript يمنحك طبقة إضافية من الأمان عبر التحقق من الأنواع، ما يقلل الأخطاء ويسرّع عملية التطوير.
لإضافة الدعم، أنشئ ملف tsconfig.json وثبّت الحزم المطلوبة ثم شغّل الخادم التطويري:
$ cd yals
$ touch tsconfig.json
$ npm install --save-dev typescript @types/react @types/node
$ npm run dev
من المفترض أن يكتشف Next.js وجود TypeScript ويضبط الإعدادات تلقائياً:
> yals@0.1.0 dev
> next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
We detected TypeScript in your project and created a tsconfig.json file for you.
بعد ذلك، غيّر امتدادات الملفات الحالية من .js إلى .tsx أو .ts بحسب نوع الملف:
$ mv pages/index.js pages/index.tsx
$ mv pages/_app.js pages/_app.tsx
$ mv pages/api/hello.js pages/api/hello.ts

الخطوة الثانية: تثبيت مكتبة واجهة المستخدم
من أهم مزايا React وفرة المكتبات الجاهزة التي تختصر وقت التطوير. بدلاً من بناء المكونات الأساسية من الصفر، سنستخدم Ant Design لأنها مرنة، ناضجة، وسهلة الدمج في المشاريع السريعة.
ثبّت المكتبة بالأمر التالي:
$ npm install --save antd
ثم أضف أنماط المكتبة إلى ملف التنسيق العام:
# file: https://github.com/mateuszsokola/yals/blob/main/styles/globals.css
@import '~antd/dist/antd.css';
بعد ذلك ستلاحظ تغير مظهر التطبيق والخطوط المستخدمة:

سنحتاج أيضاً إلى تثبيت مكتبة axios لإجراء الطلبات الشبكية بسهولة أكبر من fetch، مع دعم أفضل للأخطاء والأنواع:
$ npm install --save axios
الخطوة الثالثة: بناء واجهة التطبيق
أدخل الشيفرة التالية داخل الملف pages/index.tsx:
import Head from 'next/head'
import { useState } from 'react';
import axios, { AxiosError } from 'axios';
import { Alert, Button, Form, Input, Layout, Typography } from 'antd'
import styles from '../styles/Home.module.css'
const { Header, Content, Footer } = Layout;
const { Title } = Typography;
type ShortenLinkResponse = {
short_link: string;
}
type ShortenLinkError = {
error: string;
error_description: string;
}
type FormValues = {
link: string;
}
export default function Home() {
const [status, setStatus] = useState<'initial' | 'error' | 'success'>('initial');
const [message, setMessage] = useState('');
const [form] = Form.useForm();
const onFinish = async ({ link }: FormValues) => {
try {
const response = await axios.post('/api/shorten_link', { link });
setStatus('success');
setMessage(response.data?.short_link);
} catch(e) {
const error = e as AxiosError;
setStatus('error');
setMessage(error.response?.data?.error_description || 'Something went wrong!');
}
}
const onFinishedFailed = () => {
setStatus('error');
const error = form.getFieldError('link').join(' ');
setMessage(error);
}
return (
Yet Another Link Shortner
Copy & Paste your lengthy link
{['error', 'success'].includes(status) && (
)}
)
}
كيف تعمل هذه الواجهة؟
الواجهة تعتمد على ثلاث حالات رئيسية:
initial: عند تحميل الصفحة لأول مرة دون أي تفاعل من المستخدم.success: بعد إدخال رابط صحيح وإرسال الطلب بنجاح، حيث يظهر الرابط المختصر.error: عند حدوث مشكلة، مثل إدخال رابط غير صالح أو فشل الطلب.
الدالة onFinish مسؤولة عن إرسال الرابط إلى واجهة API، بينما تتولى الدالة onFinishedFailed عرض أخطاء التحقق من صحة النموذج. الجميل هنا أن مكوّن Form من Ant Design يعالج جزءاً كبيراً من التحقق تلقائياً.
إضافة تنسيقات الواجهة
أنشئ الملف styles/Home.module.css:
$ touch styles/Home.module.css
ثم أضف التنسيقات التالية:
.logo {
float: left;
width: 120px;
height: 30px;
margin: 16px 24px 16px 0;
background: rgba(255,255,255,.3);
}
.content {
display: flex;
align-items: center;
padding: 0 50px;
min-height: calc(100vh - 64px - 70px);
}
.shortner {
width: 100%;
background: #fff;
padding: 24px 20px;
}
.linkField {
display: flex;
width: 100%;
}
.linkFieldInput {
flex: 100%;
margin-right: 5px;
}
.linkFieldButton {
width: 120px;
}
.footer {
text-align: center;
}

الخطوة الرابعة: إعداد قاعدة بيانات MongoDB
التطبيقات عديمة الخوادم لا تحتفظ بالبيانات في الذاكرة بعد انتهاء التنفيذ، لذلك لا بد من استخدام قاعدة بيانات خارجية. اخترنا MongoDB Atlas لأنه يسهّل الإعداد ويوفر خطة مجانية مناسبة للتجارب والمشاريع الصغيرة.
بعد تسجيل الدخول إلى لوحة التحكم، أنشئ Cluster مجاني، ثم أنشئ مستخدم قاعدة بيانات جديداً عبر قسم Database Access، واختر طريقة المصادقة بكلمة مرور. احتفظ باسم المستخدم وكلمة المرور لأنك ستحتاج إليهما لاحقاً داخل رابط الاتصال.






بعد ذلك، انسخ Connection String عبر خيار Connect your application:



إعداد ملفات البيئة
أنشئ الملفين .env و.env.local:
$ touch .env
$ touch .env.local
أضف المتغير التالي إلى ملف .env:
MONGODB_URI=""
ثم أضف رابط الاتصال الحقيقي إلى ملف .env.local:
MONGODB_URI="mongodb+srv://<username>:<password>@cluster0.v56ul.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
وجود الملفين مفيد من الناحية الأمنية. فملف .env.local لا يُرفع عادةً إلى المستودع، ما يحمي بيانات الدخول من التسرب. لذلك لا تضع اسم المستخدم أو كلمة المرور في ملف .env العام.
أعد تشغيل الخادم التطويري للتأكد من تحميل المتغيرات:
Ctrl + C
$ npm run dev
إذا رأيت رسائل تؤكد تحميل الملفات البيئية، فهذا يعني أن الإعداد ناجح:
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from /Users/msokola/code/own/yals/.env.local
info - Loaded env from /Users/msokola/code/own/yals/.env
event - compiled successfully
الخطوة الخامسة: ربط التطبيق مع MongoDB
ثبت حزمة الاتصال بقاعدة البيانات:
$ npm install --save mongodb
$ npm install --save-dev @types/mongodb
أنشئ ملف pages/api/_connector.ts:
$ touch pages/api/_connector.ts
ثم أضف الشيفرة التالية:
import { MongoClient } from 'mongodb';
let cachedDb;
export async function connectToDatabase() {
if (cachedDb) {
return cachedDb;
}
const client = new MongoClient(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
cachedDb = client;
return await client.connect();
}
وظيفة هذا الملف بسيطة ومهمة: إذا كان هناك اتصال موجود بالفعل بقاعدة البيانات، فسيعيد استخدامه. وإذا لم يكن موجوداً، فسينشئ اتصالاً جديداً. هذا مهم لتحسين الأداء وتقليل إنشاء الاتصالات المتكررة في بيئة Serverless.
اختبار الاتصال عبر واجهة API
عدّل ملف pages/api/hello.ts ليصبح كالتالي:
import { connectToDatabase } from "./_connector";
export default async (req, res) => {
await connectToDatabase();
res.status(200).json({ name: 'John Doe' })
}
بعد تشغيل المشروع، افتح الرابط http://localhost:3000/api/hello. إذا ظهر الرد المتوقع، فهذا يعني أن الاتصال بقاعدة البيانات يعمل. أما إذا ظهر خطأ Internal Server Error، فراجع بيانات الاعتماد في .env.local.

الخطوة السادسة: إنشاء واجهة اختصار الروابط
أنشئ الملف pages/api/shorten_link.ts:
$ touch pages/api/shorten_link.ts
ثم أضف الشيفرة التالية:
import { connectToDatabase } from "./_connector";
export default async (req, res) => {
const db = await connectToDatabase();
if (req.body !== '' && req.body.link !== undefined && req.body.link !== '') {
const entry = await db
.db('links_db')
.collection('links_collection')
.insertOne({ link: req.body.link });
res.statusCode = 201;
return res.json({ short_link: `${process.env.VERCEL_URL}/r/${entry.insertedId}` });
}
res.statusCode = 409;
res.json({ error: 'no_link_found', error_description: 'No link found' })
}
هذه الواجهة تستقبل الرابط الأصلي من الطلب، ثم تحفظه في قاعدة البيانات، وبعدها تعيد رابطاً مختصراً يعتمد على المعرّف insertedId. وإذا لم يصل رابط صحيح، فستُعيد استجابة بخطأ 409.
أثناء التطوير المحلي قد يظهر الجزء الخاص بالرابط المختصر مسبوقاً بالقيمة undefined لأن المتغير VERCEL_URL يتوفر تلقائياً عند النشر على Vercel فقط، وهذا سلوك متوقع في هذه المرحلة.


الخطوة السابعة: إنشاء واجهة إعادة التوجيه
أنشئ الملف pages/api/redirect.ts وأضف الشيفرة التالية:
import { ObjectID } from 'mongodb';
import { connectToDatabase } from "./_connector";
export default async (req, res) => {
const db = await connectToDatabase();
const entry = await db
.db('links_db')
.collection('links_collection')
.findOne({ _id: new ObjectID(req.query.id as string) });
if (entry !== null) {
return res.redirect(301, entry.link);
}
return res.redirect(301, '/');
}
هنا يحاول النظام جلب الرابط الأصلي عبر المعرّف الموجود في query. إذا وجد السجل، ينفّذ إعادة توجيه دائمة عبر الرمز 301. وإذا لم يجد السجل، يعيد المستخدم إلى الصفحة الرئيسية.
الخطوة الثامنة: إعادة كتابة الروابط لتصبح أنظف
في وضعه الحالي، رابط إعادة التوجيه قد يكون طويلاً وغير مناسب للمستخدم النهائي، مثل:
localhost:3000/api/redirect?id=606f512cbb6d7306eb5df189
والأفضل أن يصبح بهذا الشكل:
localhost:3000/r/606f512cbb6d7306eb5df189
لتحقيق ذلك، أنشئ ملف vercel.json وضع فيه ما يلي:
{
"rewrites": [
{
"source": "/r/:id",
"destination": "/api/redirect?id=:id"
}
]
}
هذا الإعداد يخبر Vercel بأن أي طلب يصل إلى المسار /r/:id يجب إعادة توجيهه داخلياً إلى واجهة /api/redirect مع تمرير المعرّف.
الخطوة التاسعة: نشر التطبيق على Vercel
بعد الانتهاء من التطوير، ارفع المشروع إلى GitHub ثم توجه إلى Vercel وأنشئ مشروعاً جديداً من المستودع.



في إعدادات المشروع، أضف متغير البيئة MONGODB_URI بالقيمة نفسها الموجودة في ملف .env.local، ثم اضغط Deploy.

بعد انتهاء النشر، افتح التطبيق عبر زر Visit:

جرّب إدخال رابط طويل، وستلاحظ أن النظام يُنتج نسخة مختصرة قابلة للمشاركة، مثل:
- قبل الاختصار:
https://www.freecodecamp.org/news/how-to-deploy-react-apps-to-production/ - بعد الاختصار:
https://yals.vercel.app/r/606f6723622f2c0008b64dc4

أفكار لتحسين التطبيق مستقبلاً
بعد بناء النسخة الأساسية، يمكنك تطوير المشروع ليصبح أكثر احترافية وفائدة، مثل:
- إضافة أسماء مخصصة للروابط المختصرة بدلاً من الاعتماد فقط على
ObjectId. - منع تكرار الروابط المخزنة لتقليل عدد السجلات.
- إضافة صفحة إحصاءات تعرض عدد النقرات على كل رابط.
- التحقق من الروابط الضارة قبل حفظها.
- إضافة مصادقة للمستخدمين حتى يتمكن كل مستخدم من إدارة روابطه الخاصة.
أفضل ممارسات مهمة للقبول في AdSense
إذا كنت تخطط لنشر هذا النوع من المقالات على موقع يعتمد على الإعلانات، فاحرص على ما يلي:
- قدّم شرحاً أصيلاً مبنياً على تجربة أو تحليل، لا مجرد ترجمة حرفية.
- اجعل المحتوى عملياً ويتضمن أمثلة قابلة للتطبيق.
- حافظ على هيكل واضح بعناوين فرعية تسهّل القراءة.
- تجنب الفقرات المكررة أو العبارات العامة التي لا تضيف فائدة.
- استخدم صوراً ووصفاً دقيقاً يخدم القارئ ويحسن السيو في الوقت نفسه.
الخلاصة التقنية
هذا المشروع مثال ممتاز على كيفية بناء تطبيق Full-Stack Serverless بجهد معقول ووقت قصير. باستخدام Next.js حصلنا على واجهة وتطبيق خلفي ضمن مشروع واحد، ومع MongoDB Atlas أضفنا طبقة تخزين مرنة، بينما سهّل Vercel عملية النشر والتشغيل. من الناحية التقنية، هذه البنية مناسبة جداً للمشاريع الصغيرة والمتوسطة والنماذج الأولية السريعة، كما أنها تمنحك أساساً واضحاً للتوسع لاحقاً عبر إضافة التحليلات، وإدارة المستخدمين، وتحسين الأمان وتجربة الاستخدام.