الدليل الشامل لبناء واجهة برمجة تطبيقات (API) باستخدام TypeScript و AWS
مقدمة: بناء واجهة برمجة تطبيقات حديثة وفعالة
في عالم التطوير المتسارع، أصبح بناء واجهات برمجة التطبيقات (APIs) الفعالة والقابلة للتطوير أمرًا ضروريًا. يقدم هذا الدليل الشامل منهجًا عمليًا لبناء واجهة برمجة تطبيقات قوية باستخدام TypeScript و Serverless Framework على منصة AWS. سنتعمق في كيفية الاستفادة من هذه التقنيات لإنشاء واجهة برمجة تطبيقات لترجمة النصوص تلقائيًا، مما يضمن لك محتوى حصريًا وقيمة مضافة لمشروعك.
البدء: تهيئة بيئة العمل
لبدء هذه العملية، نحتاج إلى التأكد من توفر بعض المتطلبات الأساسية وتكوين بيئة العمل بشكل صحيح.
المتطلبات الأساسية
قبل الشروع في بناء مشروعنا، تأكد من تثبيت Serverless Framework وتكوين ملف تعريف AWS على جهاز الكمبيوتر الخاص بك. هذه الخطوات حاسمة لضمان إمكانية نشر وتفاعل مشروعك مع خدمات AWS. إذا لم تكن قد قمت بذلك، فمن الضروري إعداد هذه المكونات أولاً.
تهيئة المشروع باستخدام Serverless Framework
الآن، حان وقت إنشاء مشروع Serverless وواجهة برمجة التطبيقات (API) الخاصة بنا. ابدأ بفتح الطرفية (terminal) وقم بتشغيل الأمر التالي لإنشاء مستودع جديد. تأكد من استبدال بالاسم الذي تختاره لمجلد مشروعك:{YOUR FOLDER NAME}
serverless create --template aws-nodejs-typescript --path {YOUR FOLDER NAME}
سيؤدي هذا الأمر إلى إنشاء مشروع Serverless أساسي جدًا مع دعم TypeScript. عند فتح هذا المجلد الجديد باستخدام VS Code، يمكنك استكشاف الملفات التي يوفرها القالب.

فهم ملفات القالب الرئيسية
الملفات الرئيسية التي سنركز عليها هي و serverless.ts.handler.ts
serverless.ts: هذا الملف هو قلب التكوين لعملية النشر. يخبر هذا الملف إطار عملServerlessباسم المشروع، ولغة تشغيل الكود (runtime language)، وقائمة الدوال (functions)، وبعض خيارات التكوين الأخرى. في كل مرة نرغب فيها بتغيير بنية مشروعنا، سيكون هذا هو الملف الذي نعمل عليه.handler.ts: هنا نجد الكود النموذجي لدالةLambdaالذي وفره لنا القالب. إنه بسيط للغاية ويعيد استجابةAPI Gatewayمع رسالة وحدث الإدخال (input event). سنستخدم هذا لاحقًا كنقطة انطلاق لواجهة برمجة التطبيقات الخاصة بنا.
إنشاء دالة Lambda خاصة بك
بعد أن تعرفنا على ما يوفره لنا القالب، حان الوقت لإضافة دالة Lambda ونقطة نهاية (API endpoint) خاصة بنا.
هيكلة كود Lambda
للبدء، سنقوم بإنشاء مجلد جديد ليحتوي على جميع أكواد Lambda الخاصة بنا ونسميه . يساعد هذا في تنظيم المشروع، خاصة عندما تبدأ في الحصول على عدة دوال lambdasLambda مختلفة في مشروع واحد. في هذا المجلد الجديد، سنقوم بإنشاء دالة Lambda الجديدة الخاصة بنا، وسنطلق عليها اسم . عند فتح هذا الملف، يمكننا البدء في كتابة الكود الخاص بنا. يمكننا البدء بنسخ جميع أكواد getCityInfo.ts كنقطة انطلاق.handler.ts
استخلاص معاملات المسار وتغيير اسم الدالة
أول شيء سنفعله هو تغيير اسم الدالة إلى . هذا تفضيل شخصي، ولكني أفضّل تسمية الدالة التي تتعامل مع الحدث بـ handler. في السطر الأول من هذه الدالة، نحتاج إلى إضافة بعض الأكواد للحصول على اسم المدينة التي يطلبها المستخدم. يمكننا الحصول على هذا من مسار handlerURL باستخدام .pathParameters
const city = event.pathParameters?.city;
شرح السلسلة الاختيارية (Optional Chaining)
قد تلاحظ استخدام في هذا الإعلان. هذه هي ميزة السلسلة الاختيارية (?.Optional Chaining) وهي ميزة رائعة حقًا. تعني أنه إذا كان معامل المسار (path parameter) موجودًا (truthy)، فاحصل على معامل المدينة (city parameter)، وإلا فقم بإرجاع . هذا يعني أنه إذا لم يكن undefined كائنًا، فلن تحصل على الخطأ pathParameters الذي يتسبب في حدوث خطأ في وقت تشغيل cannot read property _city_ of undefinedNode.
تحديد هيكل البيانات باستخدام الواجهات (Interfaces)
الآن بعد أن أصبح لدينا اسم المدينة، نحتاج إلى التحقق من أن المدينة صالحة وأن لدينا بيانات لتلك المدينة. لهذا، نحتاج إلى بعض البيانات. يمكننا استخدام الكود أدناه ولصقه في أسفل الملف.
interface CityData {
name: string;
state: string;
description: string;
mayor: string;
population: number;
zipCodes?: string;
}
const cityData: { [key: string]: CityData } = {
newyork: {
name: 'New York',
state: 'New York',
description: 'New York City comprises 5 boroughs sitting where the Hudson River meets the Atlantic Ocean. At its core is Manhattan, a densely populated borough that’s among the world’s major commercial, financial and cultural centers. Its iconic sites include skyscrapers such as the Empire State Building and sprawling Central Park. Broadway theater is staged in neon-lit Times Square.',
mayor: 'Bill de Blasio',
population: 8399000,
zipCodes: '100xx–104xx, 11004–05, 111xx–114xx, 116xx',
},
washington: {
name: 'Washington',
state: 'District of Columbia',
description: `DescriptionWashington, DC, the U.S. capital, is a compact city on the Potomac River, bordering the states of Maryland and Virginia. It’s defined by imposing neoclassical monuments and buildings – including the iconic ones that house the federal government’s 3 branches: the Capitol, White House and Supreme Court. It's also home to iconic museums and performing-arts venues such as the Kennedy Center.`,
mayor: 'Muriel Bowser',
population: 705549,
},
seattle: {
name: 'Seattle',
state: 'Washington',
description: `DescriptionSeattle, a city on Puget Sound in the Pacific Northwest, is surrounded by water, mountains and evergreen forests, and contains thousands of acres of parkland. Washington State’s largest city, it’s home to a large tech industry, with Microsoft and Amazon headquartered in its metropolitan area. The futuristic Space Needle, a 1962 World’s Fair legacy, is its most iconic landmark.`,
mayor: 'Jenny Durkan',
population: 744955,
},
};
الفرق بين هذا و JavaScript هو أنه يمكننا إنشاء واجهة (interface) لإخبار النظام بهيكل البيانات الذي يجب أن يكون عليه. قد يبدو هذا كعمل إضافي في البداية ولكنه سيساعد في جعل كل شيء أسهل لاحقًا. داخل الواجهة الخاصة بنا، نحدد مفاتيح كائن المدينة؛ بعضها من نوع ، وواحد من نوع string، ثم number هي خاصية اختيارية (zipCodesoptional property). هذا يعني أنها قد تكون موجودة ولكن لا يجب أن تكون كذلك.
إذا أردنا اختبار الواجهة الخاصة بنا، يمكننا محاولة إضافة خاصية جديدة إلى أي من المدن في بيانات المدينة الخاصة بنا. سيخبرك TypeScript على الفور أن الخاصية الجديدة غير موجودة في الواجهة. إذا قمت بحذف إحدى الخصائص المطلوبة، فسيشتكي TypeScript أيضًا. هذا يضمن أن لديك دائمًا البيانات الصحيحة وأن الكائنات تبدو دائمًا كما هو متوقع تمامًا.
تطبيق استجابات واجهة برمجة التطبيقات (API Responses)
الآن بعد أن أصبح لدينا البيانات، يمكننا التحقق مما إذا كان المستخدم قد أرسل طلب المدينة الصحيح.
if (!city || !cityData[city]) {
// Handle error
}
إذا كانت هذه العبارة صحيحة، فهذا يعني أن المستخدم قد ارتكب خطأ ما، وبالتالي نحتاج إلى إرجاع استجابة . يمكننا فقط كتابة الكود يدويًا هنا، ولكننا سنقوم بإنشاء كائن 400 جديد مع دوال (apiResponsesmethods) لبعض أكواد استجابة API المحتملة.
const apiResponses = {
_200: (
body: { [key: string]: any }
) => {
return {
statusCode: 200,
body: JSON.stringify(body, null, 2),
};
},
_400: (
body: { [key: string]: any }
) => {
return {
statusCode: 400,
body: JSON.stringify(body, null, 2),
};
},
};
هذا يجعل إعادة الاستخدام أسهل بكثير لاحقًا في الملف. يجب أن تلاحظ أيضًا أن لدينا خاصية واحدة . هذا يشير إلى أن هذه الدالة لها خاصية واحدة من نوع body: { [key: string]: any } والتي يجب أن تكون كائنًا. يمكن أن يحتوي هذا الكائن على مفاتيح لها قيمة من أي نوع. نظرًا لأننا نعلم أن body سيكون دائمًا سلسلة نصية، يمكننا استخدام body للتأكد من أننا نعيد جسمًا نصيًا.JSON.stringify
إذا أضفنا هذه الدالة إلى الخاص بنا، فسنحصل على هذا:handler
export const handler: APIGatewayProxyHandler = async (event, _context) => {
const city = event.pathParameters?.city;
if (!city || !cityData[city]) {
return apiResponses._400({ message: 'missing city or no data for that city' });
}
return apiResponses._200(cityData[city]);
};
إذا لم يقم المستخدم بتمرير مدينة أو مرر مدينة لا توجد لدينا بيانات لها، فإننا نعيد استجابة مع رسالة خطأ. إذا كانت البيانات موجودة، فإننا نعيد استجابة 400 مع جسم يحتوي على البيانات.200
إضافة واجهة برمجة تطبيقات ترجمة جديدة
في القسم السابق، قمنا بإعداد مستودع API TypeScript الخاص بنا وأنشأنا دالة Lambda تستخدم بيانات مبرمجة ثابتة. سيعلمك هذا الجزء كيفية استخدام للتفاعل مباشرة مع خدمات aws-sdkAWS الأخرى لإنشاء واجهة برمجة تطبيقات قوية حقًا.
إعداد دالة Lambda للترجمة
للبدء، نحتاج إلى إضافة ملف جديد لواجهة برمجة تطبيقات الترجمة الخاصة بنا. أنشئ ملفًا جديدًا ضمن مجلد باسم lambdas. يمكننا البدء بهذا الملف ببعض الأكواد الأساسية. هذا هو الكود الأولي لدالة translate.tsLambda API TypeScript.
import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';
export const handler: APIGatewayProxyHandler = async (event) => {
// Translation logic will go here
};
تحليل جسم الطلب (Parsing Request Body)
الآن نحتاج إلى الحصول على النص الذي يرغب المستخدم في ترجمته واللغة التي يريد الترجمة إليها. يمكننا الحصول على هذه البيانات من جسم الطلب (body of the request). شيء إضافي يجب علينا فعله هنا هو تحليل الجسم (parse the body). بشكل افتراضي، يقوم API Gateway بتحويل أي JSON يتم تمريره في الجسم إلى سلسلة نصية. يمكننا بعد ذلك استخراج و text من الجسم.language
const body = JSON.parse(event.body);
const { text, language } = body;
الآن نحتاج إلى التحقق مما إذا كان المستخدم قد مرر النص واللغة.
if (!text) {
// return 400
}
if (!language) {
// return 400
}
مركزية استجابات واجهة برمجة التطبيقات (Centralizing API Responses)
في الجزء الأخير، أنشأنا استجابة كدالة في الملف. نظرًا لأننا سنستخدم استجابات 400API هذه عبر ملفات متعددة، فمن الجيد سحبها إلى ملف مشترك خاص بها. أنشئ مجلدًا جديدًا ضمن باسم lambdas. هذا هو المكان الذي سنخزن فيه جميع الدوال المشتركة. في هذا المجلد، أنشئ ملفًا جديدًا باسم common. سيقوم هذا الملف بتصدير الكائن apiResponses.ts مع دوال apiResponses و _200 عليه. إذا كان عليك إرجاع أكواد استجابة أخرى، فيمكنك إضافتها إلى هذا الكائن._400
const apiResponses = {
_200: (
body: { [key: string]: any }
) => {
return {
statusCode: 200,
body: JSON.stringify(body, null, 2),
};
},
_400: (
body: { [key: string]: any }
) => {
return {
statusCode: 400,
body: JSON.stringify(body, null, 2),
};
},
};
export default apiResponses;
يمكننا الآن استيراد هذا الكائن إلى الكود الخاص بنا واستخدام هذه الدوال المشتركة. في أعلى ملف الخاص بنا، يمكننا الآن إضافة هذا السطر:translate.ts
import apiResponses from './common/apiResponses';
وتحديث عمليات التحقق من و text لاستدعاء دالة language على هذا الكائن:_400
if (!text) {
return apiResponses._400({ message: 'missing text from the body' });
}
if (!language) {
return apiResponses._400({ message: 'missing language from the body' });
}
بعد الانتهاء من ذلك، نعلم أن لدينا النص المراد ترجمته واللغة المراد الترجمة إليها، لذا يمكننا بدء عملية الترجمة. يعد استخدام دائمًا تقريبًا مهمة غير متزامنة (aws-sdkasync task)، لذا سنقوم بتغليفه في كتلة لتسهيل معالجة الأخطاء.try/catch
try {
// Translation logic
} catch (error) {
// Error handling
}
دمج AWS SDK للترجمة
أول شيء نحتاج إلى فعله هو استيراد وإنشاء مثيل جديد لخدمة الترجمة (aws-sdktranslate service). للقيام بذلك، نحتاج إلى تثبيت ثم استيراده. أولاً، قم بتشغيل aws-sdk ثم أضف هذا الكود إلى أعلى ملف الترجمة الخاص بك:npm install --save aws-sdk
import * as AWS from 'aws-sdk';
const translate = new AWS.Translate();
بهذا، يمكننا البدء في كتابة كود الترجمة الخاص بنا. سنبدأ بالسطر الذي يقوم بالترجمة أولاً. أضف هذا في قسم .try
const translatedMessage = await translate.translateText(translateParams).promise();
شيء قد لاحظه البعض منكم هو أننا نمرر دون تعريفه بعد. هذا لأننا لسنا متأكدين من نوعه بعد. لمعرفة ذلك، يمكننا استخدام أداة في translateParamsVS Code تسمى . تتيح لنا هذه الأداة الانتقال إلى حيث يتم تعريف الدالة حتى نتمكن من معرفة نوع المعاملات. يمكنك إما النقر بزر الماوس الأيمن وتحديد go to definition أو الضغط على go to definition والنقر على الدالة.Ctrl

كما ترى، تأخذ دالة معاملًا من نوع translateText. طريقة أخرى لمعرفة ذلك هي استخدام Translate.Types.TranslateTextRequestIntelliSense عن طريق تحريك مؤشر الماوس فوق دالة . يجب أن ترى هذا، حيث يمكنك رؤية أن translateText:params: AWS.Translate.TranslateTextRequest

بهذا، يمكننا إنشاء معاملات الترجمة الخاصة بنا فوق طلب الترجمة الذي قمنا به سابقًا. يمكننا بعد ذلك ملؤها بناءً على النوع الذي نحدده. هذا يضمن أننا نمرر الحقول الصحيحة.
const translateParams: AWS.Translate.Types.TranslateTextRequest = {
Text: text,
SourceLanguageCode: 'en',
TargetLanguageCode: language,
};
الآن بعد أن أصبح لدينا المعاملات ونقوم بتمريرها إلى دالة ، يمكننا البدء في إنشاء استجابتنا. ستكون هذه مجرد استجابة translate.translateText مع الرسالة المترجمة.200
return apiResponses._200({ translatedMessage });
بعد الانتهاء من كل ذلك، يمكننا الانتقال إلى قسم . هنا، نريد فقط تسجيل الخطأ ثم إرجاع استجابة catch من الملف المشترك.400
console.log('error in the translation', error);
return apiResponses._400({ message: 'unable to translate the message' });
بعد الانتهاء من ذلك، انتهينا من كود Lambda الخاص بنا، لذا نحتاج إلى الانتقال إلى ملف لإضافة نقطة نهاية serverless.tsAPI الجديدة هذه ومنحها الأذونات التي تحتاجها.
تكوين Serverless لواجهة برمجة تطبيقات الترجمة
في ملف ، يمكننا التمرير لأسفل إلى قسم serverless.ts. هنا نحتاج إلى إضافة دالة جديدة إلى الكائن.functions
translate: {
handler: 'lambdas/translate.handler',
events: [
{
http: {
path: 'translate',
method: 'POST',
cors: true,
},
},
],
},
الفرق الرئيسي بين هذا ونقطة النهاية السابقة هو أن نقطة النهاية الآن هي طريقة . هذا يعني أنه إذا حاولت إجراء طلب POST إلى مسار GETURL هذا، فستحصل على استجابة خطأ.
الشيء الأخير الذي يجب فعله هو منح دوال Lambda الإذن لاستخدام خدمة الترجمة (Translate service). مع جميع خدمات AWS تقريبًا، ستحتاج إلى إضافة أذونات إضافية لتتمكن من استخدامها من داخل دالة Lambda. للقيام بذلك، نضيف حقلًا جديدًا إلى قسم يسمى provider. هذه مصفوفة من عبارات السماح (iamRoleStatementsallow) أو الرفض (deny) لخدمات وموارد مختلفة.
iamRoleStatements: [
{
Effect: 'Allow',
Action: ['translate:*'],
Resource: '*',
},
],
اختبار واجهة برمجة تطبيقات الترجمة
مع إضافة هذا، أصبح لدينا كل ما نحتاجه جاهزًا، لذا يمكننا تشغيل لنشر واجهة برمجة التطبيقات الجديدة الخاصة بنا. بمجرد نشرها، يمكننا الحصول على sls deployURL الخاص بواجهة برمجة التطبيقات واستخدام أداة مثل أو Postman لإجراء طلب إلى postwoman.ioURL هذا. نحتاج فقط إلى تمرير جسم (body) على النحو التالي:
{
"text": "This is a test message for translation",
"language": "fr"
}
وبعد ذلك يجب أن نحصل على استجابة على النحو التالي:200
{
"translatedMessage": {
"TranslatedText": "Ceci est un message de test pour la traduction",
"SourceLanguageCode": "en",
"TargetLanguageCode": "fr"
}
}
ملخص
في هذا المقال، تعلمنا كيفية:
- إعداد مستودع
TypeScriptجديد باستخدام الأمر.serverless create --template aws-nodejs-typescript - إضافة دالة
Lambdaخاصة بنا تعيد مجموعة من البيانات المبرمجة ثابتة. - إضافة دالة
Lambdaهذه كنقطة نهاية (API endpoint). - إضافة دالة
Lambdaأخرى ستقوم تلقائيًا بترجمة أي نص يتم تمريره إليها. - إضافة نقطة نهاية
APIومنح دالةLambdaالأذونات التي تحتاجها للعمل.
إذا استمتعت بهذا المقال وترغب في معرفة المزيد عن Serverless و AWS، فإنني أوصي بمشاهدة مقاطع الفيديو الأكثر إثارة للاهتمام في قائمة التشغيل الخاصة بي حول Serverless و AWS.
الخلاصة التقنية
لقد أثبت هذا الدليل بوضوح كيف يمكن لدمج TypeScript و Serverless Framework على AWS أن يحدث ثورة في طريقة بناء واجهات برمجة التطبيقات. إن قوة TypeScript في توفير السلامة النوعية (type safety) تقلل الأخطاء وتزيد من قابلية صيانة الكود، بينما تتيح بنية Serverless بناء تطبيقات قابلة للتوسع (scalable) وفعالة من حيث التكلفة. القدرة على دمج خدمات AWS SDK مثل AWS Translate مباشرة في دوال Lambda تفتح آفاقًا واسعة لإنشاء ميزات غنية ومعقدة بسهولة. هذا النهج لا يسرع عملية التطوير فحسب، بل يضمن أيضًا مرونة عالية واستجابة لاحتياجات العمل المتغيرة، مما يجعله خيارًا مثاليًا للمشاريع الحديثة التي تتطلب أداءً عاليًا وكفاءة تشغيلية.