1. खास जानकारी
इस कोडलैब का मकसद, Google Cloud Platform की "बिना सर्वर के काम करने वाली" सेवाओं के बारे में जानकारी पाना है:
- Cloud Functions — फ़ंक्शन के तौर पर कारोबार के लॉजिक की छोटी यूनिट डिप्लॉय करने के लिए. ये यूनिट, अलग-अलग इवेंट (Pub/Sub मैसेज, Cloud Storage में नई फ़ाइलें, एचटीटीपी अनुरोध वगैरह) पर प्रतिक्रिया करती हैं,
- App Engine — इसका इस्तेमाल वेब ऐप्लिकेशन, वेब एपीआई, मोबाइल बैकएंड, और स्टैटिक ऐसेट को डिप्लॉय और सर्व करने के लिए किया जाता है. इसमें तेज़ी से स्केल अप और स्केल डाउन करने की सुविधाएं होती हैं,
- Cloud Run — इसका इस्तेमाल कंटेनर को डिप्लॉय और स्केल करने के लिए किया जाता है. इनमें किसी भी भाषा, रनटाइम या लाइब्रेरी का इस्तेमाल किया जा सकता है.
साथ ही, यह भी जानें कि वेब और REST API को डिप्लॉय और स्केल करने के लिए, सर्वरलेस सेवाओं का फ़ायदा कैसे लिया जा सकता है. इसके अलावा, कुछ अच्छे RESTful डिज़ाइन सिद्धांतों के बारे में भी जानें.
इस वर्कशॉप में, हम एक बुकशेल्फ़ एक्सप्लोरर बनाएंगे. इसमें ये चीज़ें शामिल होंगी:
- Cloud फ़ंक्शन: हमारी लाइब्रेरी में मौजूद किताबों के शुरुआती डेटासेट को Cloud Firestore दस्तावेज़ डेटाबेस में इंपोर्ट करने के लिए,
- Cloud Run कंटेनर: यह हमारे डेटाबेस के कॉन्टेंट पर REST API को ऐक्सेस करने की अनुमति देगा,
- App Engine वेब फ़्रंटएंड: हमारी REST API को कॉल करके, किताबों की सूची ब्राउज़ करने के लिए.
इस कोडलैब के आखिर में, वेब फ़्रंटएंड ऐसा दिखेगा:

आपको क्या सीखने को मिलेगा
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
2. सेटअप और ज़रूरी शर्तें
अपने हिसाब से एनवायरमेंट सेट अप करना
- Google Cloud Console में साइन इन करें और नया प्रोजेक्ट बनाएं या किसी मौजूदा प्रोजेक्ट का फिर से इस्तेमाल करें. अगर आपके पास पहले से कोई Gmail या Google Workspace खाता नहीं है, तो आपको एक खाता बनाना होगा.



- प्रोजेक्ट का नाम, इस प्रोजेक्ट में हिस्सा लेने वाले लोगों के लिए डिसप्ले नेम होता है. यह एक वर्ण स्ट्रिंग है, जिसका इस्तेमाल Google API नहीं करते. इसे कभी भी अपडेट किया जा सकता है.
- प्रोजेक्ट आईडी, सभी Google Cloud प्रोजेक्ट के लिए यूनीक होता है. साथ ही, इसे बदला नहीं जा सकता. Cloud Console, यूनीक स्ट्रिंग को अपने-आप जनरेट करता है. आम तौर पर, आपको इससे कोई फ़र्क़ नहीं पड़ता कि यह क्या है. ज़्यादातर कोडलैब में, आपको अपने प्रोजेक्ट आईडी (आम तौर पर
PROJECT_IDके तौर पर पहचाना जाता है) का रेफ़रंस देना होगा. अगर आपको जनरेट किया गया आईडी पसंद नहीं है, तो कोई दूसरा रैंडम आईडी जनरेट किया जा सकता है. इसके अलावा, आपके पास अपना नाम आज़माने का विकल्प भी है. इससे आपको पता चलेगा कि वह नाम उपलब्ध है या नहीं. इस चरण के बाद, इसे बदला नहीं जा सकता. यह प्रोजेक्ट की अवधि तक बना रहता है. - आपकी जानकारी के लिए बता दें कि एक तीसरी वैल्यू भी होती है, जिसे प्रोजेक्ट नंबर कहते हैं. इसका इस्तेमाल कुछ एपीआई करते हैं. इन तीनों वैल्यू के बारे में ज़्यादा जानने के लिए, दस्तावेज़ देखें.
- इसके बाद, आपको Cloud Console में बिलिंग चालू करनी होगी, ताकि Cloud संसाधनों/एपीआई का इस्तेमाल किया जा सके. इस कोडलैब को पूरा करने में ज़्यादा समय नहीं लगेगा. इस ट्यूटोरियल के बाद बिलिंग से बचने के लिए, संसाधनों को बंद किया जा सकता है. इसके लिए, बनाए गए संसाधनों को मिटाएं या प्रोजेक्ट को मिटाएं. Google Cloud के नए उपयोगकर्ताओं को, 300 डॉलर का क्रेडिट मिलेगा. वे इसे मुफ़्त में आज़मा सकते हैं.
Cloud Shell शुरू करें
Google Cloud को अपने लैपटॉप से रिमोटली ऐक्सेस किया जा सकता है. हालांकि, इस कोडलैब में Google Cloud Shell का इस्तेमाल किया जाएगा. यह क्लाउड में चलने वाला कमांड लाइन एनवायरमेंट है.
Google Cloud Console में, सबसे ऊपर दाएं कोने में मौजूद टूलबार पर, Cloud Shell आइकॉन पर क्लिक करें:

इसे चालू करने और एनवायरमेंट से कनेक्ट करने में सिर्फ़ कुछ सेकंड लगेंगे. यह प्रोसेस पूरी होने के बाद, आपको कुछ ऐसा दिखेगा:

इस वर्चुअल मशीन में, डेवलपमेंट के लिए ज़रूरी सभी टूल पहले से मौजूद हैं. यह 5 जीबी की होम डायरेक्ट्री उपलब्ध कराता है. साथ ही, यह Google Cloud पर काम करता है. इससे नेटवर्क की परफ़ॉर्मेंस और पुष्टि करने की प्रोसेस बेहतर होती है. इस कोडलैब में मौजूद सभी टास्क, ब्राउज़र में किए जा सकते हैं. आपको कुछ भी इंस्टॉल करने की ज़रूरत नहीं है.
3. एनवायरमेंट तैयार करना और क्लाउड एपीआई चालू करना
इस प्रोजेक्ट के दौरान हमें जिन सेवाओं की ज़रूरत होगी उनके लिए, हम कुछ एपीआई चालू करेंगे. इसके लिए, हम Cloud Shell में यह कमांड लॉन्च करेंगे:
$ gcloud services enable \
appengine.googleapis.com \
cloudbuild.googleapis.com \
cloudfunctions.googleapis.com \
compute.googleapis.com \
firestore.googleapis.com \
run.googleapis.com
कुछ समय बाद, आपको यह दिखेगा कि ऑपरेशन पूरा हो गया है:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
हम एक एनवायरमेंट वैरिएबल भी सेट अप करेंगे, जिसकी हमें आगे ज़रूरत पड़ेगी: वह क्लाउड क्षेत्र जहां हम अपने फ़ंक्शन, ऐप्लिकेशन, और कंटेनर को डिप्लॉय करेंगे:
$ export REGION=europe-west3
हम डेटा को Cloud Firestore डेटाबेस में सेव करेंगे. इसलिए, हमें डेटाबेस बनाना होगा:
$ gcloud app create --region=${REGION}
$ gcloud firestore databases create --location=${REGION}
इस कोडलैब में आगे चलकर, REST API लागू करते समय हमें डेटा को क्रम से लगाने और फ़िल्टर करने की ज़रूरत होगी. इसके लिए, हम तीन इंडेक्स बनाएंगे:
$ gcloud firestore indexes composite create --collection-group=books \
--field-config field-path=language,order=ascending \
--field-config field-path=updated,order=descending
$ gcloud firestore indexes composite create --collection-group=books \
--field-config field-path=author,order=ascending \
--field-config field-path=updated,order=descending
ये तीन इंडेक्स, लेखक या भाषा के हिसाब से की जाने वाली खोजों से जुड़े हैं. साथ ही, अपडेट किए गए फ़ील्ड के ज़रिए कलेक्शन में क्रम बनाए रखते हैं.
4. कोड प्राप्त करें
नीचे दी गई Github रिपॉज़िटरी से कोड पाएं:
$ git clone https://github.com/glaforge/serverless-web-apis
ऐप्लिकेशन कोड को Node.JS का इस्तेमाल करके लिखा गया है.
आपके पास इस लैब के लिए, फ़ोल्डर का यह स्ट्रक्चर होगा:
serverless-web-apis
|
├── data
| ├── books.json
|
├── function-import
| ├── index.js
| ├── package.json
|
├── run-crud
| ├── index.js
| ├── package.json
| ├── Dockerfile
|
├── appengine-frontend
├── public
| ├── css/style.css
| ├── html/index.html
| ├── js/app.js
├── index.js
├── package.json
├── app.yaml
ये काम के फ़ोल्डर हैं:
data— इस फ़ोल्डर में, 100 किताबों की सूची का सैंपल डेटा है.function-import— यह फ़ंक्शन, सैंपल डेटा इंपोर्ट करने के लिए एक एंडपॉइंट उपलब्ध कराएगा.run-crud— यह कंटेनर, Cloud Firestore में सेव की गई किताब के डेटा को ऐक्सेस करने के लिए, एक वेब एपीआई उपलब्ध कराएगा.appengine-frontend— यह App Engine वेब ऐप्लिकेशन, किताबों की सूची ब्राउज़ करने के लिए सिर्फ़ पढ़ने वाला फ़्रंटएंड दिखाएगा.
5. किताब की लाइब्रेरी के डेटा का सैंपल
डेटा फ़ोल्डर में, हमारे पास एक books.json फ़ाइल है. इसमें 100 किताबों की सूची है. शायद ये किताबें पढ़ने लायक हैं. यह JSON दस्तावेज़, JSON ऑब्जेक्ट वाली एक सारणी है. आइए, उस डेटा के बारे में जानते हैं जिसे हम Cloud Functions के ज़रिए शामिल करेंगे:
[
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
},
{
"isbn": "9781414251196",
"author": "Hans Christian Andersen",
"language": "Danish",
"pages": 784,
"title": "Fairy tales",
"year": 1836
},
...
]
इस कलेक्शन में मौजूद हमारी सभी किताबों की एंट्री में यह जानकारी शामिल होती है:
isbn— यह किताब की पहचान करने वाला ISBN-13 कोड है.author— किताब के लेखक का नाम.language— वह भाषा जिसमें किताब लिखी गई है.pages— किताब में पेजों की संख्या.title— किताब का टाइटल.year— किताब के पब्लिश होने का साल.
6. किताब के सैंपल का डेटा इंपोर्ट करने के लिए फ़ंक्शन एंडपॉइंट
इस पहले सेक्शन में, हम उस एंडपॉइंट को लागू करेंगे जिसका इस्तेमाल, किताब के सैंपल डेटा को इंपोर्ट करने के लिए किया जाएगा. इसके लिए, हम Cloud Functions का इस्तेमाल करेंगे.
कोड के बारे में ज़्यादा जानें
आइए, package.json फ़ाइल से शुरुआत करते हैं:
{
"name": "function-import",
"description": "Import sample book data",
"license": "Apache-2.0",
"dependencies": {
"@google-cloud/firestore": "^4.9.9"
},
"devDependencies": {
"@google-cloud/functions-framework": "^3.1.0"
},
"scripts": {
"start": "npx @google-cloud/functions-framework --target=parseBooks"
}
}
रनटाइम डिपेंडेंसी में, हमें डेटाबेस को ऐक्सेस करने और अपनी किताब का डेटा सेव करने के लिए, सिर्फ़ @google-cloud/firestore NPM मॉड्यूल की ज़रूरत होती है. Cloud Functions का रनटाइम, Express वेब फ़्रेमवर्क भी उपलब्ध कराता है. इसलिए, हमें इसे डिपेंडेंसी के तौर पर एलान करने की ज़रूरत नहीं है.
डेवलपमेंट डिपेंडेंसी में, हम Functions Framework (@google-cloud/functions-framework) का एलान करते हैं. यह रनटाइम फ़्रेमवर्क है, जिसका इस्तेमाल आपके फ़ंक्शन को शुरू करने के लिए किया जाता है. यह एक ओपन सोर्स फ़्रेमवर्क है. इसका इस्तेमाल, अपनी मशीन पर स्थानीय तौर पर भी किया जा सकता है. हमारे मामले में, Cloud Shell में इसका इस्तेमाल किया जा सकता है. इससे, हर बार बदलाव करने पर फ़ंक्शन को डिप्लॉय किए बिना चलाया जा सकता है. इससे डेवलपमेंट फ़ीडबैक लूप को बेहतर बनाया जा सकता है.
डिपेंडेंसी इंस्टॉल करने के लिए, install कमांड का इस्तेमाल करें:
$ npm install
start स्क्रिप्ट, Functions Framework का इस्तेमाल करती है. इससे आपको एक ऐसा निर्देश मिलता है जिसका इस्तेमाल करके, फ़ंक्शन को स्थानीय तौर पर चलाया जा सकता है. इसके लिए, यह निर्देश दिया जाता है:
$ npm start
फ़ंक्शन के साथ इंटरैक्ट करने के लिए, एचटीटीपी GET अनुरोधों के लिए curl या Cloud Shell की वेब झलक का इस्तेमाल किया जा सकता है.
अब हम index.js फ़ाइल पर एक नज़र डालते हैं. इसमें हमारी किताब के डेटा को इंपोर्ट करने वाले फ़ंक्शन का लॉजिक शामिल है:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
हम Firestore मॉड्यूल को इंस्टैंशिएट करते हैं और किताबों के कलेक्शन (रिलेशनल डेटाबेस में टेबल की तरह) की ओर इशारा करते हैं.
functions.http('parseBooks', async (req, resp) => {
if (req.method !== "POST") {
resp.status(405).send({error: "Only method POST allowed"});
return;
}
if (req.headers['content-type'] !== "application/json") {
resp.status(406).send({error: "Only application/json accepted"});
return;
}
...
})
हम parseBooks JavaScript फ़ंक्शन को एक्सपोर्ट कर रहे हैं. यह वह फ़ंक्शन है जिसे हम बाद में डिप्लॉय करते समय एलान करेंगे.
अगले कुछ निर्देशों में यह जांच की जा रही है कि:
- हम सिर्फ़ एचटीटीपी
POSTअनुरोधों को स्वीकार करते हैं. इसके अलावा, हम405स्टेटस कोड दिखाते हैं, ताकि यह पता चल सके कि एचटीटीपी के अन्य तरीकों का इस्तेमाल करने की अनुमति नहीं है. - हम सिर्फ़
application/jsonपेलोड स्वीकार करते हैं. इसके अलावा, हम406स्टेटस कोड भेजते हैं, ताकि यह बताया जा सके कि यह पेलोड फ़ॉर्मैट स्वीकार नहीं किया जा सकता.
const books = req.body;
const writeBatch = firestore.batch();
for (const book of books) {
const doc = bookStore.doc(book.isbn);
writeBatch.set(doc, {
title: book.title,
author: book.author,
language: book.language,
pages: book.pages,
year: book.year,
updated: Firestore.Timestamp.now()
});
}
इसके बाद, हम अनुरोध के body के ज़रिए JSON पेलोड को वापस पा सकते हैं. हम एक Firestore बैच ऑपरेशन तैयार कर रहे हैं, ताकि सभी किताबों को एक साथ सेव किया जा सके. हम किताब की जानकारी वाले JSON ऐरे को दोहराते हैं. इसमें isbn, title, author, language, pages, और year फ़ील्ड शामिल हैं. किताब का ISBN कोड, उसकी प्राइमरी कुंजी या आइडेंटिफ़ायर के तौर पर काम करेगा.
try {
await writeBatch.commit();
console.log("Saved books in Firestore");
} catch (e) {
console.error("Error saving books:", e);
resp.status(400).send({error: "Error saving books"});
return;
};
resp.status(202).send({status: "OK"});
अब ज़्यादातर डेटा तैयार है. इसलिए, हम कार्रवाई पूरी कर सकते हैं. अगर स्टोरेज की प्रोसेस पूरी नहीं होती है, तो हम 400 स्टेटस कोड दिखाते हैं. इससे पता चलता है कि प्रोसेस पूरी नहीं हुई. इसके अलावा, हम 202 स्टेटस कोड के साथ OK रिस्पॉन्स दे सकते हैं. इससे पता चलता है कि एक साथ कई प्रॉडक्ट सेव करने का अनुरोध स्वीकार कर लिया गया है.
इंपोर्ट फ़ंक्शन को चलाना और उसकी जांच करना
कोड चलाने से पहले, हम इन डिपेंडेंसी को इंस्टॉल करेंगे:
$ npm install
Functions Framework की मदद से, फ़ंक्शन को स्थानीय तौर पर चलाने के लिए, हम start स्क्रिप्ट कमांड का इस्तेमाल करेंगे. इसे हमने package.json में तय किया था:
$ npm start > start > npx @google-cloud/functions-framework --target=parseBooks Serving function... Function: parseBooks URL: http://localhost:8080/
अपने लोकल फ़ंक्शन पर एचटीटीपी POST अनुरोध भेजने के लिए, यह कमांड चलाएं:
$ curl -d "@../data/books.json" \
-H "Content-Type: application/json" \
http://localhost:8080/
इस कमांड को लॉन्च करने पर, आपको यह आउटपुट दिखेगा. इससे पुष्टि होती है कि फ़ंक्शन स्थानीय तौर पर चल रहा है:
{"status":"OK"}
Cloud Console के यूज़र इंटरफ़ेस (यूआई) पर जाकर भी यह देखा जा सकता है कि डेटा Firestore में सेव है या नहीं:

ऊपर दिए गए स्क्रीनशॉट में, हमें books बनाया गया कलेक्शन, किताब के ISBN कोड से पहचाने गए किताब के दस्तावेज़ों की सूची, और दाईं ओर उस किताब की एंट्री की जानकारी दिख रही है.
क्लाउड में फ़ंक्शन डिप्लॉय करना
Cloud Functions में फ़ंक्शन को डिप्लॉय करने के लिए, हम function-import डायरेक्ट्री में इस कमांड का इस्तेमाल करेंगे:
$ gcloud functions deploy bulk-import \
--gen2 \
--trigger-http \
--runtime=nodejs20 \
--allow-unauthenticated \
--max-instances=30
--region=${REGION} \
--source=. \
--entry-point=parseBooks
हम फ़ंक्शन को bulk-import के सिंबल वाले नाम से डिप्लॉय करते हैं. इस फ़ंक्शन को एचटीटीपी अनुरोधों के ज़रिए ट्रिगर किया जाता है. हम Node.JS 20 रनटाइम का इस्तेमाल करते हैं. हम फ़ंक्शन को सार्वजनिक तौर पर डिप्लॉय करते हैं. हालांकि, हमें उस एंडपॉइंट को सुरक्षित रखना चाहिए. हम उस क्षेत्र के बारे में बताते हैं जहां हमें फ़ंक्शन को रखना है. साथ ही, हम लोकल डायरेक्ट्री में मौजूद सोर्स की ओर इशारा करते हैं और parseBooks (एक्सपोर्ट किया गया JavaScript फ़ंक्शन) को एंट्री पॉइंट के तौर पर इस्तेमाल करते हैं.
कुछ मिनटों में या इससे कम समय में, फ़ंक्शन को क्लाउड में डिप्लॉय कर दिया जाता है. Cloud Console के यूज़र इंटरफ़ेस (यूआई) में, आपको यह फ़ंक्शन दिखना चाहिए:

डिप्लॉयमेंट आउटपुट में, आपको अपने फ़ंक्शन का यूआरएल दिखेगा. यह यूआरएल, नाम रखने के एक तय नियम (https://${REGION}-${GOOGLE_CLOUD_PROJECT}.cloudfunctions.net/${FUNCTION_NAME}) के मुताबिक होता है. साथ ही, इस एचटीटीपी ट्रिगर यूआरएल को Cloud Console के यूज़र इंटरफ़ेस (यूआई) में, ट्रिगर टैब में भी देखा जा सकता है:

gcloud की मदद से, कमांड-लाइन के ज़रिए भी यूआरएल को वापस लाया जा सकता है:
$ export BULK_IMPORT_URL=$(gcloud functions describe bulk-import \
--region=$REGION \
--format 'value(httpsTrigger.url)')
$ echo $BULK_IMPORT_URL
इसे BULK_IMPORT_URL एनवायरमेंट वैरिएबल में सेव करते हैं, ताकि हम इसे डिप्लॉय किए गए फ़ंक्शन की जांच करने के लिए दोबारा इस्तेमाल कर सकें.
डिप्लॉय किए गए फ़ंक्शन की जांच करना
हम उसी कर्ल कमांड का इस्तेमाल करेंगे जिसका इस्तेमाल हमने पहले, फ़ंक्शन को लोकल तौर पर चलाने की जांच करने के लिए किया था. इससे हम डिप्लॉय किए गए फ़ंक्शन की जांच करेंगे. सिर्फ़ यूआरएल में बदलाव किया जाएगा:
$ curl -d "@../data/books.json" \
-H "Content-Type: application/json" \
$BULK_IMPORT_URL
अगर यह प्रोसेस पूरी हो जाती है, तो आपको यह आउटपुट दिखेगा:
{"status":"OK"}
इंपोर्ट करने का फ़ंक्शन डिप्लॉय हो गया है और इस्तेमाल के लिए तैयार है. हमने सैंपल डेटा भी अपलोड कर दिया है. अब हमें इस डेटासेट को दिखाने वाला REST API डेवलप करना है.
7. REST API का कानूनी समझौता
हालांकि, हम एपीआई अनुबंध को तय नहीं कर रहे हैं. उदाहरण के लिए, Open API specification का इस्तेमाल करके, हम अपने REST API के अलग-अलग एंडपॉइंट पर एक नज़र डालने वाले हैं.
एपीआई, किताब के JSON ऑब्जेक्ट का आदान-प्रदान करता है. इनमें ये शामिल हैं:
isbn(ज़रूरी नहीं) — यह 13 वर्णों वालाStringहोता है, जो मान्य ISBN कोड को दिखाता है,author— एक ऐसाStringजिसमें किताब के लेखक का नाम मौजूद हो,language— एक ऐसाStringजिसमें किताब की भाषा के बारे में जानकारी दी गई हो,pages— किताब के पेजों की संख्या के लिए पॉज़िटिवInteger,title— एक ऐसाStringजिसमें किताब का टाइटल मौजूद हो,year— यह किताब के प्रकाशन के साल के लिएIntegerवैल्यू है.
ई-बुक के पेलोड का उदाहरण:
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
}
GET /books
सभी किताबों की सूची पाएं. इसे लेखक और/या भाषा के हिसाब से फ़िल्टर किया जा सकता है. साथ ही, एक बार में 10 नतीजों के हिसाब से पेज पर दिखाया जा सकता है.
बॉडी पेलोड: कोई नहीं.
क्वेरी पैरामीटर:
author(ज़रूरी नहीं) — इससे लेखक के हिसाब से किताबों की सूची को फ़िल्टर किया जाता है,language(ज़रूरी नहीं) — इससे भाषा के हिसाब से किताबों की सूची को फ़िल्टर किया जाता है,page(ज़रूरी नहीं, डिफ़ॉल्ट = 0) — इससे नतीजों के उस पेज की रैंक का पता चलता है जिसे दिखाना है.
यह फ़ंक्शन, किताबों के ऑब्जेक्ट का JSON कलेक्शन दिखाता है.
स्टेटस कोड:
200— जब किताबों की सूची फ़ेच करने का अनुरोध पूरा हो जाता है,400— अगर कोई गड़बड़ी होती है.
POST /books और POST /books/{isbn}
नई किताब का पेलोड पोस्ट करें. इसके लिए, isbn पाथ पैरामीटर का इस्तेमाल किया जा सकता है. इस मामले में, किताब के पेलोड में isbn कोड की ज़रूरत नहीं होती. इसके अलावा, isbn पाथ पैरामीटर का इस्तेमाल किए बिना भी पेलोड पोस्ट किया जा सकता है. इस मामले में, किताब के पेलोड में isbn कोड मौजूद होना चाहिए
बॉडी पेलोड: एक बुक ऑब्जेक्ट.
क्वेरी पैरामीटर: कोई नहीं.
वापसी: कुछ नहीं.
स्टेटस कोड:
201— जब किताब को सेव कर लिया जाता है,406— अगरisbnकोड अमान्य है, तो400— अगर कोई गड़बड़ी होती है.
GET /books/{isbn}
यह लाइब्रेरी से किसी किताब को वापस पाने का तरीका है. इसके लिए, पाथ पैरामीटर के तौर पर पास किए गए isbn कोड का इस्तेमाल किया जाता है.
बॉडी पेलोड: कोई नहीं.
क्वेरी पैरामीटर: कोई नहीं.
जवाब: एक बुक JSON ऑब्जेक्ट या अगर किताब मौजूद नहीं है, तो गड़बड़ी का ऑब्जेक्ट.
स्टेटस कोड:
200— अगर किताब डेटाबेस में मिलती है, तो400— अगर कोई गड़बड़ी होती है, तो404— अगर किताब नहीं मिली, तो406— अगरisbnकोड अमान्य है.
PUT /books/{isbn}
यह isbn को पाथ पैरामीटर के तौर पर पास करके, किसी मौजूदा किताब को अपडेट करता है.
बॉडी पेलोड: एक बुक ऑब्जेक्ट. सिर्फ़ उन फ़ील्ड को पास किया जा सकता है जिन्हें अपडेट करने की ज़रूरत है. बाकी फ़ील्ड ज़रूरी नहीं हैं.
क्वेरी पैरामीटर: कोई नहीं.
नतीजा: अपडेट की गई किताब.
स्टेटस कोड:
200— जब किताब को अपडेट कर दिया जाता है,400— अगर कोई गड़बड़ी होती है, तो406— अगरisbnकोड अमान्य है.
DELETE /books/{isbn}
यह अनुरोध, isbn के तौर पर पास किए गए पाथ पैरामीटर से पहचाने गए मौजूदा पब्लिकेशन को मिटा देता है.
बॉडी पेलोड: कोई नहीं.
क्वेरी पैरामीटर: कोई नहीं.
वापसी: कुछ नहीं.
स्टेटस कोड:
204— जब किताब को मिटा दिया जाता है,400— अगर कोई गड़बड़ी होती है.
8. कंटेनर में REST API डिप्लॉय करना और उसे उपलब्ध कराना
कोड के बारे में ज़्यादा जानें
Dockerfile
आइए, सबसे पहले Dockerfile को देखते हैं. यह हमारे ऐप्लिकेशन कोड को कंटेनर में रखने के लिए ज़िम्मेदार होगा:
FROM node:20-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . ./
CMD [ "node", "index.js" ]
हम Node.JS 20 की "slim" इमेज का इस्तेमाल कर रहे हैं. हम /usr/src/app डायरेक्ट्री में काम कर रहे हैं. हम package.json फ़ाइल को कॉपी कर रहे हैं. इसमें अन्य चीज़ों के साथ-साथ, हमारी डिपेंडेंसी के बारे में भी बताया गया है. इसके बारे में यहां बताया गया है. हम सोर्स कोड को कॉपी करके, npm install की मदद से डिपेंडेंसी इंस्टॉल करते हैं. आखिर में, हम node index.js कमांड की मदद से यह बताते हैं कि इस ऐप्लिकेशन को कैसे चलाया जाना चाहिए.
package.json
इसके बाद, हम package.json फ़ाइल देख सकते हैं:
{
"name": "run-crud",
"description": "CRUD operations over book data",
"license": "Apache-2.0",
"engines": {
"node": ">= 20.0.0"
},
"dependencies": {
"@google-cloud/firestore": "^4.9.9",
"cors": "^2.8.5",
"express": "^4.17.1",
"isbn3": "^1.1.10"
},
"scripts": {
"start": "node index.js"
}
}
हम यह बताते हैं कि हमें Node.JS 14 का इस्तेमाल करना है, जैसा कि Dockerfile के मामले में था.
हमारा वेब एपीआई ऐप्लिकेशन इन पर निर्भर करता है:
- डेटाबेस में मौजूद किताब के डेटा को ऐक्सेस करने के लिए, Firestore NPM मॉड्यूल,
corsलाइब्रेरी का इस्तेमाल, CORS (क्रॉस ऑरिजिन रिसॉर्स शेयरिंग) अनुरोधों को मैनेज करने के लिए किया जाता है. ऐसा इसलिए, क्योंकि हमारे REST API को App Engine वेब ऐप्लिकेशन के फ़्रंटएंड के क्लाइंट कोड से शुरू किया जाएगा,- Express फ़्रेमवर्क, जो हमारे एपीआई को डिज़ाइन करने के लिए हमारा वेब फ़्रेमवर्क होगा,
- इसके बाद,
isbn3मॉड्यूल है. यह मॉड्यूल, किताब के ISBN कोड की पुष्टि करने में मदद करता है.
हम start स्क्रिप्ट भी तय करते हैं. यह स्क्रिप्ट, ऐप्लिकेशन को स्थानीय तौर पर शुरू करने के लिए काम आएगी. इसका इस्तेमाल डेवलपमेंट और टेस्टिंग के लिए किया जा सकता है.
index.js
अब हम कोड के मुख्य हिस्से पर आते हैं. इसमें index.js के बारे में ज़्यादा जानकारी दी गई है:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
हमें Firestore मॉड्यूल की ज़रूरत होती है. साथ ही, हम books कलेक्शन को रेफ़रंस करते हैं, जहां हमारी किताबों का डेटा सेव होता है.
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
const querystring = require('querystring');
const cors = require('cors');
app.use(cors({
exposedHeaders: ['Content-Length', 'Content-Type', 'Link'],
}));
हम अपने REST API को लागू करने के लिए, Express का इस्तेमाल वेब फ़्रेमवर्क के तौर पर कर रहे हैं. हम अपने एपीआई के साथ शेयर किए गए JSON पेलोड को पार्स करने के लिए, body-parser मॉड्यूल का इस्तेमाल कर रहे हैं.
यूआरएल में बदलाव करने के लिए, querystring मॉड्यूल का इस्तेमाल किया जाता है. ऐसा तब होता है, जब हम पेज पर नंबर डालने के लिए Link हेडर बनाते हैं. इसके बारे में ज़्यादा जानकारी बाद में दी जाएगी.
इसके बाद, हम cors मॉड्यूल को कॉन्फ़िगर करते हैं. हम उन हेडर के बारे में साफ़ तौर पर बताते हैं जिन्हें हमें सीओआरएस के ज़रिए पास करना है, क्योंकि ज़्यादातर हेडर हटा दिए जाते हैं. हालांकि, यहां हमें कॉन्टेंट की सामान्य लंबाई और टाइप के साथ-साथ Link हेडर को भी बनाए रखना है. हम पेज नंबर के हिसाब से कॉन्टेंट दिखाने के लिए, इस हेडर के बारे में जानकारी देंगे.
const ISBN = require('isbn3');
function isbnOK(isbn, res) {
const parsedIsbn = ISBN.parse(isbn);
if (!parsedIsbn) {
res.status(406)
.send({error: `Invalid ISBN: ${isbn}`});
return false;
}
return parsedIsbn;
}
हम ISBN कोड को पार्स और पुष्टि करने के लिए, isbn3 NPM मॉड्यूल का इस्तेमाल करेंगे. साथ ही, हम एक छोटा यूटिलिटी फ़ंक्शन डेवलप करेंगे, जो ISBN कोड को पार्स करेगा. अगर ISBN कोड अमान्य हैं, तो यह फ़ंक्शन जवाब के तौर पर 406 स्टेटस कोड दिखाएगा.
GET /books
आइए, GET /books एंडपॉइंट के हर हिस्से पर एक नज़र डालें:
app.get('/books', async (req, res) => {
try {
var query = new Firestore().collection('books');
if (!!req.query.author) {
console.log(`Filtering by author: ${req.query.author}`);
query = query.where("author", "==", req.query.author);
}
if (!!req.query.language) {
console.log(`Filtering by language: ${req.query.language}`);
query = query.where("language", "==", req.query.language);
}
const page = parseInt(req.query.page) || 0;
// - - ✄ - - ✄ - - ✄ - - ✄ - - ✄ - -
} catch (e) {
console.error('Failed to fetch books', e);
res.status(400)
.send({error: `Impossible to fetch books: ${e.message}`});
}
});
हम क्वेरी तैयार करके, डेटाबेस से क्वेरी करने की तैयारी कर रहे हैं. यह क्वेरी, क्वेरी के उन पैरामीटर पर निर्भर करेगी जिन्हें इस्तेमाल करना ज़रूरी नहीं है. इससे लेखक और/या भाषा के हिसाब से फ़िल्टर किया जा सकेगा. हम किताबों की सूची को भी 10-10 किताबों के हिस्सों में बांटकर दिखा रहे हैं.
अगर किताबें फ़ेच करते समय कोई गड़बड़ी होती है, तो हम 400 स्टेटस कोड वाली गड़बड़ी का मैसेज दिखाते हैं.
आइए, उस एंडपॉइंट के काटे गए हिस्से को ज़ूम इन करके देखते हैं:
const snapshot = await query
.orderBy('updated', 'desc')
.limit(PAGE_SIZE)
.offset(PAGE_SIZE * page)
.get();
const books = [];
if (snapshot.empty) {
console.log('No book found');
} else {
snapshot.forEach(doc => {
const {title, author, pages, year, language, ...otherFields} = doc.data();
const book = {isbn: doc.id, title, author, pages, year, language};
books.push(book);
});
}
पिछले सेक्शन में, हमने author और language के हिसाब से फ़िल्टर किया था. हालांकि, इस सेक्शन में हम किताबों की सूची को, आखिरी बार अपडेट किए जाने की तारीख के हिसाब से क्रम से लगाएंगे. इसमें, सबसे पहले आखिरी बार अपडेट की गई किताब दिखेगी. साथ ही, हम नतीजे को पेज के हिसाब से भी दिखाएंगे. इसके लिए, हम एक सीमा (दिखाए जाने वाले एलिमेंट की संख्या) और ऑफ़सेट (वह शुरुआती पॉइंट जहां से किताबों का अगला बैच दिखाया जाना है) तय करेंगे.
हम क्वेरी को एक्ज़ीक्यूट करते हैं, डेटा का स्नैपशॉट लेते हैं, और उन नतीजों को JavaScript ऐरे में डालते हैं. यह ऐरे, फ़ंक्शन के आखिर में दिखेगा.
आइए, इस एंडपॉइंट के बारे में जानकारी देने का काम पूरा करें. इसके लिए, हम एक सबसे सही तरीका देखेंगे: डेटा के पहले, पिछले, अगले या आखिरी पेजों के यूआरआई लिंक तय करने के लिए, Link हेडर का इस्तेमाल करना. हमारे मामले में, हम सिर्फ़ पिछले और अगले पेज के लिंक देंगे.
var links = {};
if (page > 0) {
const prevQuery = querystring.stringify({...req.query, page: page - 1});
links.prev = `${req.path}${prevQuery != '' ? `?${prevQuery}` : ''}`;
}
if (snapshot.docs.length === PAGE_SIZE) {
const nextQuery = querystring.stringify({...req.query, page: page + 1});
links.next = `${req.path}${nextQuery != '' ? `?${nextQuery}` : ''}`;
}
if (Object.keys(links).length > 0) {
res.links(links);
}
res.status(200).send(books);
यहां लॉजिक थोड़ा मुश्किल लग सकता है. हालांकि, हम डेटा के पहले पेज पर न होने पर, previous लिंक जोड़ रहे हैं. अगर डेटा का पेज पूरा भर गया है (यानी कि इसमें PAGE_SIZE कॉन्स्टेंट के हिसाब से किताबों की ज़्यादा से ज़्यादा संख्या है), तो हम अगला लिंक जोड़ते हैं. ऐसा तब किया जाता है, जब हमें लगता है कि ज़्यादा डेटा वाला कोई दूसरा पेज आने वाला है. इसके बाद, हम Express के resource#links() फ़ंक्शन का इस्तेमाल करके, सही सिंटैक्स वाला हेडर बनाते हैं.
आपकी जानकारी के लिए बता दें कि लिंक हेडर कुछ ऐसा दिखेगा:
link: </books?page=1>; rel="prev", </books?page=3>; rel="next"
POST /booksऔरPOST /books/:isbn
नई किताब बनाने के लिए, दोनों एंडपॉइंट यहां दिए गए हैं. एक, किताब के पेलोड में ISBN कोड पास करता है, जबकि दूसरा इसे पाथ पैरामीटर के तौर पर पास करता है. दोनों ही मामलों में, हमारे createBook() फ़ंक्शन को कॉल किया जाता है:
async function createBook(isbn, req, res) {
const parsedIsbn = isbnOK(isbn, res);
if (!parsedIsbn) return;
const {title, author, pages, year, language} = req.body;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.set({
title, author, pages, year, language,
updated: Firestore.Timestamp.now()
});
console.log(`Saved book ${parsedIsbn.isbn13}`);
res.status(201)
.location(`/books/${parsedIsbn.isbn13}`)
.send({status: `Book ${parsedIsbn.isbn13} created`});
} catch (e) {
console.error(`Failed to save book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to create book ${parsedIsbn.isbn13}: ${e.message}`});
}
}
हम यह जांच करते हैं कि isbn कोड मान्य है या नहीं. अगर यह मान्य नहीं है, तो हम फ़ंक्शन से बाहर निकल जाते हैं और 406 स्टेटस कोड सेट करते हैं. हम अनुरोध के मुख्य हिस्से में पास किए गए पेलोड से, किताब के फ़ील्ड वापस पाते हैं. इसके बाद, हम किताब की जानकारी को Firestore में सेव करेंगे. सफल होने पर 201 और असफल होने पर 400 दिखाता है.
सफलतापूर्वक जवाब मिलने पर, हम लोकेशन हेडर भी सेट करते हैं, ताकि एपीआई के क्लाइंट को यह जानकारी मिल सके कि नई बनाई गई संसाधन कहां है. हेडर इस तरह दिखेगा:
Location: /books/9781234567898
GET /books/:isbn
आइए, Firestore से ISBN के ज़रिए पहचानी गई कोई किताब फ़ेच करते हैं.
app.get('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
const docSnapshot = await docRef.get();
if (!docSnapshot.exists) {
console.log(`Book not found ${parsedIsbn.isbn13}`)
res.status(404)
.send({error: `Could not find book ${parsedIsbn.isbn13}`});
return;
}
console.log(`Fetched book ${parsedIsbn.isbn13}`, docSnapshot.data());
const {title, author, pages, year, language, ...otherFields} = docSnapshot.data();
const book = {isbn: parsedIsbn.isbn13, title, author, pages, year, language};
res.status(200).send(book);
} catch (e) {
console.error(`Failed to fetch book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to fetch book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
हम हमेशा की तरह यह देखते हैं कि ISBN मान्य है या नहीं. हम किताब को वापस पाने के लिए, Firestore से क्वेरी करते हैं. snapshot.exists प्रॉपर्टी से यह पता चलता है कि कोई किताब मिली है या नहीं. अगर ऐसा नहीं होता है, तो हम गड़बड़ी का मैसेज और 404 'नहीं मिला' स्टेटस कोड वापस भेजते हैं. हम किताब का डेटा वापस पाते हैं और किताब को दिखाने के लिए, एक JSON ऑब्जेक्ट बनाते हैं.
PUT /books/:isbn
हम किसी मौजूदा किताब को अपडेट करने के लिए, PUT तरीके का इस्तेमाल कर रहे हैं.
app.put('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.set({
...req.body,
updated: Firestore.Timestamp.now()
}, {merge: true});
console.log(`Updated book ${parsedIsbn.isbn13}`);
res.status(201)
.location(`/books/${parsedIsbn.isbn13}`)
.send({status: `Book ${parsedIsbn.isbn13} updated`});
} catch (e) {
console.error(`Failed to update book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to update book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
हम updated तारीख/समय फ़ील्ड को अपडेट करते हैं, ताकि हमें यह याद रहे कि हमने उस रिकॉर्ड को आखिरी बार कब अपडेट किया था. हम {merge:true} रणनीति का इस्तेमाल करते हैं. इससे मौजूदा फ़ील्ड को उनकी नई वैल्यू से बदल दिया जाता है. ऐसा न करने पर, सभी फ़ील्ड हटा दिए जाते हैं और सिर्फ़ पेलोड में मौजूद नए फ़ील्ड सेव किए जाते हैं. इससे पिछले अपडेट या शुरुआती क्रिएशन के मौजूदा फ़ील्ड मिट जाते हैं.
हम Location हेडर को भी सेट करते हैं, ताकि वह किताब के यूआरआई पर ले जाए.
DELETE /books/:isbn
किताबों को मिटाना बहुत आसान है. हम सिर्फ़ दस्तावेज़ के रेफ़रंस पर delete() तरीके को कॉल करते हैं. हम 204 स्टेटस कोड दिखाते हैं, क्योंकि हम कोई कॉन्टेंट नहीं दिखा रहे हैं.
app.delete('/books/:isbn', async (req, res) => {
const parsedIsbn = isbnOK(req.params.isbn, res);
if (!parsedIsbn) return;
try {
const docRef = bookStore.doc(parsedIsbn.isbn13);
await docRef.delete();
console.log(`Book ${parsedIsbn.isbn13} was deleted`);
res.status(204).end();
} catch (e) {
console.error(`Failed to delete book ${parsedIsbn.isbn13}`, e);
res.status(400)
.send({error: `Impossible to delete book ${parsedIsbn.isbn13}: ${e.message}`});
}
});
Express / Node सर्वर शुरू करना
आखिर में, हम सर्वर को चालू करते हैं. यह डिफ़ॉल्ट रूप से पोर्ट 8080 पर काम करता है:
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Books Web API service: listening on port ${port}`);
console.log(`Node ${process.version}`);
});
ऐप्लिकेशन को स्थानीय तौर पर चलाना
ऐप्लिकेशन को स्थानीय तौर पर चलाने के लिए, हम सबसे पहले इन डिपेंडेंसी को इंस्टॉल करेंगे:
$ npm install
इसके बाद, हम यह शुरू कर सकते हैं:
$ npm start
सर्वर localhost को शुरू होगा और डिफ़ॉल्ट रूप से पोर्ट 8080 पर सिग्नल पाने के लिए कॉन्फ़िगर किया जाएगा.
नीचे दी गई कमांड का इस्तेमाल करके, Docker कंटेनर बनाया जा सकता है. साथ ही, कंटेनर इमेज भी चलाई जा सकती है:
$ docker build -t crud-web-api . $ docker run --rm -p 8080:8080 -it crud-web-api
Docker में ऐप्लिकेशन को चलाने से, यह भी पक्का किया जा सकता है कि हमारे ऐप्लिकेशन का कंटेनर ठीक से काम करेगा. ऐसा इसलिए, क्योंकि हम इसे Cloud Build की मदद से क्लाउड में बनाते हैं.
एपीआई की जांच करना
REST API कोड को सीधे तौर पर Node के ज़रिए चलाया जाए या Docker कंटेनर इमेज के ज़रिए, अब हम इसके ख़िलाफ़ कुछ क्वेरी चला सकते हैं.
- नई किताब बनाएं (बॉडी पेलोड में ISBN):
$ curl -XPOST -d '{"isbn":"9782070368228","title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books
- नई किताब बनाएं (पाथ पैरामीटर में ISBN):
$ curl -XPOST -d '{"title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books/9782070368228
- हमने जो किताब बनाई है उसे मिटाने के लिए:
$ curl -XDELETE http://localhost:8080/books/9782070368228
- ISBN के हिसाब से कोई किताब ढूंढना:
$ curl http://localhost:8080/books/9780140449136 $ curl http://localhost:8080/books/9782070360536
- सिर्फ़ टाइटल बदलकर, किसी मौजूदा किताब को अपडेट करने के लिए:
$ curl -XPUT \
-d '{"title":"Book"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books/9780003701203
- किताबों की सूची (पहले 10):
$ curl http://localhost:8080/books
- किसी लेखक की किताबें ढूंढने के लिए:
$ curl http://localhost:8080/books?author=Virginia+Woolf
- अंग्रेज़ी में लिखी गई किताबें दिखाओ:
$ curl http://localhost:8080/books?language=English
- किताबों का चौथा पेज लोड करो:
$ curl http://localhost:8080/books?page=3
हम अपनी खोज को बेहतर बनाने के लिए, author, language, और books क्वेरी पैरामीटर को भी एक साथ इस्तेमाल कर सकते हैं.
कंटेनर में मौजूद REST API बनाना और उसे डिप्लॉय करना
हमें खुशी है कि REST API, प्लान के मुताबिक काम कर रहा है. इसलिए, इसे Cloud Run पर क्लाउड में डिप्लॉय करने का यह सही समय है!
हम इसे दो चरणों में करेंगे:
- सबसे पहले, Cloud Build का इस्तेमाल करके कंटेनर इमेज बनाएं. इसके लिए, यह कमांड इस्तेमाल करें:
$ gcloud builds submit \
--tag gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api
- इसके बाद, इस दूसरी कमांड का इस्तेमाल करके सेवा को डिप्लॉय करें:
$ gcloud run deploy run-crud \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api \
--allow-unauthenticated \
--region=${REGION} \
--platform=managed
पहली कमांड की मदद से, Cloud Build कंटेनर इमेज बनाता है और उसे Container Registry में होस्ट करता है. अगली कमांड, कंटेनर इमेज को रजिस्ट्री से डिप्लॉय करती है. साथ ही, इसे क्लाउड क्षेत्र में डिप्लॉय करती है.
Cloud Console के यूज़र इंटरफ़ेस (यूआई) में जाकर, हम यह पुष्टि कर सकते हैं कि हमारी Cloud Run सेवा अब सूची में दिख रही है:

यहां हम आखिरी चरण पूरा करेंगे. इसके लिए, हम नई डिप्लॉय की गई Cloud Run सेवा का यूआरएल वापस पाएंगे. इसके लिए, हम इस कमांड का इस्तेमाल करेंगे:
$ export RUN_CRUD_SERVICE_URL=$(gcloud run services describe run-crud \
--region=${REGION} \
--platform=managed \
--format='value(status.url)')
हमें अगले सेक्शन में, Cloud Run REST API के यूआरएल की ज़रूरत होगी. ऐसा इसलिए, क्योंकि हमारा App Engine फ़्रंटएंड कोड, एपीआई के साथ इंटरैक्ट करेगा.
9. लाइब्रेरी ब्राउज़ करने के लिए, वेब ऐप्लिकेशन होस्ट करना
इस प्रोजेक्ट को और बेहतर बनाने के लिए, हमें एक वेब फ़्रंटएंड की ज़रूरत होगी. यह वेब फ़्रंटएंड, हमारे REST API के साथ इंटरैक्ट करेगा. इसके लिए, हम Google App Engine का इस्तेमाल करेंगे. साथ ही, कुछ क्लाइंट JavaScript कोड का इस्तेमाल करेंगे. यह कोड, क्लाइंट-साइड Fetch API का इस्तेमाल करके, AJAX अनुरोधों के ज़रिए एपीआई को कॉल करेगा.
हमारा ऐप्लिकेशन, Node.JS App Engine रनटाइम पर डिप्लॉय किया गया है. हालांकि, यह ज़्यादातर स्टैटिक संसाधनों से बना है! इसमें ज़्यादा बैकएंड कोड नहीं होता, क्योंकि उपयोगकर्ता के ज़्यादातर इंटरैक्शन, क्लाइंट-साइड JavaScript के ज़रिए ब्राउज़र में होते हैं. हम किसी भी फ़ैंसी फ़्रंटएंड JavaScript फ़्रेमवर्क का इस्तेमाल नहीं करेंगे. हम सिर्फ़ कुछ "वैनिला" JavaScript का इस्तेमाल करेंगे. साथ ही, यूज़र इंटरफ़ेस (यूआई) के लिए कुछ वेब कॉम्पोनेंट का इस्तेमाल करेंगे. इसके लिए, हम Shoelace वेब कॉम्पोनेंट लाइब्रेरी का इस्तेमाल करेंगे:
- किताब की भाषा चुनने के लिए, एक 'चुने' बॉक्स:

- किसी किताब के बारे में जानकारी दिखाने के लिए कार्ड कॉम्पोनेंट (इसमें किताब के ISBN को दिखाने के लिए बारकोड भी शामिल है. इसके लिए, JsBarcode लाइब्रेरी का इस्तेमाल किया गया है):

- और डेटाबेस से ज़्यादा किताबें लोड करने के लिए एक बटन:

इन सभी विज़ुअल कॉम्पोनेंट को एक साथ जोड़ने पर, हमारी लाइब्रेरी ब्राउज़ करने के लिए वेब पेज ऐसा दिखेगा:

app.yaml कॉन्फ़िगरेशन फ़ाइल
आइए, इस App Engine ऐप्लिकेशन के कोड बेस के बारे में ज़्यादा जानें. इसके लिए, इसकी app.yaml कॉन्फ़िगरेशन फ़ाइल देखें. यह App Engine के लिए खास तौर पर बनाई गई फ़ाइल है. इसकी मदद से, एनवायरमेंट वैरिएबल, ऐप्लिकेशन के अलग-अलग "हैंडलर" जैसे कॉम्पोनेंट कॉन्फ़िगर किए जा सकते हैं. इसके अलावा, यह भी तय किया जा सकता है कि कुछ संसाधन स्टैटिक ऐसेट हैं, जिन्हें App Engine के बिल्ट-इन सीडीएन से डिलीवर किया जाएगा.
runtime: nodejs14
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
handlers:
- url: /js
static_dir: public/js
- url: /css
static_dir: public/css
- url: /img
static_dir: public/img
- url: /(.+\.html)
static_files: public/html/\1
upload: public/(.+\.html)
- url: /
static_files: public/html/index.html
upload: public/html/index\.html
- url: /.*
secure: always
script: auto
हम यह बताते हैं कि हमारा ऐप्लिकेशन Node.JS है और हमें वर्शन 14 का इस्तेमाल करना है.
इसके बाद, हम एक एनवायरमेंट वैरिएबल तय करते हैं, जो हमारी Cloud Run सेवा के यूआरएल की ओर इशारा करता है. हमें CHANGE_ME प्लेसहोल्डर को सही यूआरएल से अपडेट करना होगा. इसे बदलने का तरीका यहां दिया गया है.
इसके बाद, हम अलग-अलग हैंडलर तय करते हैं. पहले तीन पॉइंटर, क्लाइंट-साइड कोड की लोकेशन पर ले जाते हैं. यह कोड, एचटीएमएल, सीएसएस, और JavaScript में लिखा गया है. यह public/ फ़ोल्डर और इसके सब-फ़ोल्डर में मौजूद होता है. चौथे यूआरएल से पता चलता है कि हमारे App Engine ऐप्लिकेशन का रूट यूआरएल, index.html पेज पर रीडायरेक्ट होना चाहिए. इस तरह, वेबसाइट के रूट को ऐक्सेस करते समय, हमें यूआरएल में index.html सफ़िक्स नहीं दिखेगा. आखिरी वाला डिफ़ॉल्ट है. यह अन्य सभी यूआरएल (/.*) को हमारे Node.JS ऐप्लिकेशन पर रीडायरेक्ट करेगा. यानी, ऐप्लिकेशन के "डाइनैमिक" हिस्से पर. यह उन स्टैटिक ऐसेट के उलट है जिनके बारे में हमने बताया है.
अब Cloud Run सेवा के Web API यूआरएल को अपडेट करते हैं.
appengine-frontend/ डायरेक्ट्री में, Cloud Run पर आधारित REST API के यूआरएल की ओर इशारा करने वाले एनवायरमेंट वैरिएबल को अपडेट करने के लिए, यह कमांड चलाएं:
$ sed -i -e "s|CHANGE_ME|${RUN_CRUD_SERVICE_URL}|" app.yaml
इसके अलावा, CHANGE_ME में मौजूद CHANGE_ME स्ट्रिंग को सही यूआरएल से मैन्युअल तरीके से बदलें:app.yaml
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
Node.JS package.json फ़ाइल
{
"name": "appengine-frontend",
"description": "Web frontend",
"license": "Apache-2.0",
"main": "index.js",
"engines": {
"node": "^14.0.0"
},
"dependencies": {
"express": "^4.17.1",
"isbn3": "^1.1.10"
},
"devDependencies": {
"nodemon": "^2.0.7"
},
"scripts": {
"start": "node index.js",
"dev": "nodemon --watch server --inspect index.js"
}
}
हम फिर से इस बात पर ज़ोर देते हैं कि हमें इस ऐप्लिकेशन को Node.JS 14 का इस्तेमाल करके चलाना है. हम Express फ़्रेमवर्क के साथ-साथ, isbn3 NPM मॉड्यूल पर भी निर्भर करते हैं. इसकी मदद से, किताबों के ISBN कोड की पुष्टि की जाती है.
डेवलपमेंट डिपेंडेंसी में, हम फ़ाइल में होने वाले बदलावों को मॉनिटर करने के लिए nodemon मॉड्यूल का इस्तेमाल करेंगे. npm start की मदद से, हम अपने ऐप्लिकेशन को स्थानीय तौर पर चला सकते हैं. साथ ही, कोड में कुछ बदलाव कर सकते हैं. इसके बाद, ^C की मदद से ऐप्लिकेशन को बंद करके फिर से लॉन्च किया जा सकता है. हालांकि, यह थोड़ा मुश्किल है. इसके बजाय, हम इस कमांड का इस्तेमाल कर सकते हैं, ताकि बदलाव होने पर ऐप्लिकेशन अपने-आप रिलोड / रीस्टार्ट हो जाए:
$ npm run dev
index.js Node.JS कोड
const express = require('express');
const app = express();
app.use(express.static('public'));
const bodyParser = require('body-parser');
app.use(bodyParser.json());
हमें Express वेब फ़्रेमवर्क की ज़रूरत है. हम यह बताते हैं कि सार्वजनिक डायरेक्ट्री में ऐसी स्टैटिक ऐसेट होती हैं जिन्हें static मिडलवेयर से दिखाया जा सकता है. कम से कम तब, जब डेवलपमेंट मोड में स्थानीय तौर पर चलाया जा रहा हो. आखिर में, हमें अपने JSON पेलोड को पार्स करने के लिए body-parser की ज़रूरत होती है.
आइए, उन रास्तों पर एक नज़र डालें जिन्हें हमने तय किया है:
app.get('/', async (req, res) => {
res.redirect('/html/index.html');
});
app.get('/webapi', async (req, res) => {
res.send(process.env.RUN_CRUD_SERVICE_URL);
});
/ से मेल खाने वाला पहला लिंक, हमारी public/html डायरेक्ट्री में मौजूद index.html पर रीडायरेक्ट करेगा. डेवलपमेंट मोड में, हम App Engine रनटाइम में नहीं चल रहे होते हैं. इसलिए, हमें App Engine की यूआरएल राउटिंग नहीं मिलती है. इसलिए, यहां हम रूट यूआरएल को एचटीएमएल फ़ाइल पर रीडायरेक्ट कर रहे हैं.
हमारा दूसरा एंडपॉइंट /webapi, Cloud RUN REST API का यूआरएल दिखाएगा. इस तरह, क्लाइंट-साइड JavaScript कोड को पता चल जाएगा कि किताबों की सूची पाने के लिए कहां कॉल करना है.
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Book library web frontend: listening on port ${port}`);
console.log(`Node ${process.version}`);
console.log(`Web API endpoint ${process.env.RUN_CRUD_SERVICE_URL}`);
});
आखिर में, हम Express वेब ऐप्लिकेशन चला रहे हैं और डिफ़ॉल्ट रूप से पोर्ट 8080 पर सुन रहे हैं.
index.html पेज
हम इस लंबे एचटीएमएल पेज की हर लाइन को नहीं देखेंगे. इसके बजाय, आइए कुछ मुख्य लाइनों पर ध्यान दें.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.37/dist/themes/base.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.37/dist/shoelace.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.0/dist/barcodes/JsBarcode.ean-upc.min.js"></script>
<script src="/js/app.js"></script>
<link rel="stylesheet" type="text/css" href="/css/style.css">
पहली दो लाइनों में, Shoelace वेब कॉम्पोनेंट लाइब्रेरी (एक स्क्रिप्ट और एक स्टाइलशीट) इंपोर्ट की जाती है.
अगली लाइन में, JsBarcode लाइब्रेरी को इंपोर्ट किया जाता है. इससे किताब के ISBN कोड के बारकोड बनाए जा सकते हैं.
आखिरी लाइनें, हमारे अपने JavaScript कोड और सीएसएस स्टाइलशीट को इंपोर्ट कर रही हैं. ये हमारी public/ सबडायरेक्ट्री में मौजूद हैं.
एचटीएमएल पेज के body में, हम Shoelace कॉम्पोनेंट का इस्तेमाल करते हैं. इनके कस्टम एलिमेंट टैग इस तरह होते हैं:
<sl-icon name="book-half"></sl-icon>
...
<sl-select id="language-select" placeholder="Select a language..." clearable>
<sl-menu-item value="English">English</sl-menu-item>
<sl-menu-item value="French">French</sl-menu-item>
...
</sl-select>
...
<sl-button id="more-button" type="primary" size="large">
More books...
</sl-button>
...
हम किसी किताब को दिखाने के लिए, एचटीएमएल टेंप्लेट और उनकी स्लॉट भरने की सुविधा का भी इस्तेमाल करते हैं. हम किताबों की सूची भरने के लिए, उस टेंप्लेट की कॉपी बनाएंगे. साथ ही, स्लॉट में मौजूद वैल्यू को किताबों की जानकारी से बदल देंगे:
<template id="book-card">
<sl-card class="card-overview">
...
<slot name="author">Author</slot>
...
</sl-card>
</template>
बहुत हो गया एचटीएमएल, अब हम कोड की समीक्षा पूरी करने वाले हैं. एक आखिरी अहम हिस्सा बचा है: app.js क्लाइंट-साइड JavaScript कोड, जो हमारे REST API के साथ इंटरैक्ट करता है.
app.js क्लाइंट-साइड JavaScript कोड
हम सबसे पहले टॉप-लेवल इवेंट लिसनर का इस्तेमाल करते हैं. यह DOM कॉन्टेंट के लोड होने का इंतज़ार करता है:
document.addEventListener("DOMContentLoaded", async function(event) {
...
}
तैयार हो जाने के बाद, हम कुछ मुख्य कॉन्स्टेंट और वैरिएबल सेट अप कर सकते हैं:
const serverUrlResponse = await fetch('/webapi');
const serverUrl = await serverUrlResponse.text();
console.log('Web API endpoint:', serverUrl);
const server = serverUrl + '/books';
var page = 0;
var language = '';
सबसे पहले, हम अपने REST API का यूआरएल फ़ेच करेंगे. इसके लिए, हम App Engine नोड कोड का इस्तेमाल करेंगे. यह कोड, उस एनवायरमेंट वैरिएबल को दिखाता है जिसे हमने app.yaml में सेट किया था. एनवायरमेंट वैरिएबल की वजह से, हमें अपने फ़्रंटएंड कोड में REST API यूआरएल को हार्डकोड नहीं करना पड़ा. इस वैरिएबल की मदद से, JavaScript क्लाइंट-साइड कोड से /webapi एंडपॉइंट को कॉल किया जाता है.
हम page और language वैरिएबल भी तय करते हैं. इनका इस्तेमाल, पेज नंबर के हिसाब से नतीजे दिखाने और भाषा के हिसाब से फ़िल्टर करने के लिए किया जाएगा.
const moreButton = document.getElementById('more-button');
moreButton.addEventListener('sl-focus', event => {
console.log('Button clicked');
moreButton.blur();
appendMoreBooks(server, page++, language);
});
हम किताबों को लोड करने के लिए, बटन पर एक इवेंट हैंडलर जोड़ते हैं. इस पर क्लिक करने से, appendMoreBooks() फ़ंक्शन कॉल होगा.
const langSelect = document.getElementById('language-select');
langSelect.addEventListener('sl-change', event => {
page = 0;
language = event.srcElement.value;
document.getElementById('library').replaceChildren();
console.log(`Language selected: "${language}"`);
appendMoreBooks(server, page++, language);
});
इसी तरह, हम select बॉक्स के लिए एक इवेंट हैंडलर जोड़ते हैं, ताकि भाषा चुनने के विकल्प में होने वाले बदलावों के बारे में सूचना मिल सके. बटन की तरह ही, हम appendMoreBooks() फ़ंक्शन को भी कॉल करते हैं. इसमें REST API यूआरएल, मौजूदा पेज, और भाषा चुनने का विकल्प पास किया जाता है.
इसलिए, आइए उस फ़ंक्शन पर एक नज़र डालते हैं जो किताबों को फ़ेच और जोड़ता है:
async function appendMoreBooks(server, page, language) {
const searchUrl = new URL(server);
if (!!page) searchUrl.searchParams.append('page', page);
if (!!language) searchUrl.searchParams.append('language', language);
const response = await fetch(searchUrl.href);
const books = await response.json();
...
}
ऊपर, हम REST API को कॉल करने के लिए सटीक यूआरएल बना रहे हैं. आम तौर पर, तीन क्वेरी पैरामीटर तय किए जा सकते हैं. हालांकि, इस यूज़र इंटरफ़ेस (यूआई) में सिर्फ़ दो पैरामीटर तय किए गए हैं:
page— एक पूर्णांक, जो किताबों के पेज नंबर के हिसाब से क्रम लगाने के लिए मौजूदा पेज को दिखाता है,language— यह एक भाषा स्ट्रिंग है. इसका इस्तेमाल, लिखी गई भाषा के हिसाब से फ़िल्टर करने के लिए किया जाता है.
इसके बाद, हम Fetch API का इस्तेमाल करके, JSON फ़ॉर्मैट में मौजूद उस कलेक्शन को वापस पाते हैं जिसमें हमारी किताब की जानकारी होती है.
const linkHeader = response.headers.get('Link')
console.log('Link', linkHeader);
if (!!linkHeader && linkHeader.indexOf('rel="next"') > -1) {
console.log('Show more button');
document.getElementById('buttons').style.display = 'block';
} else {
console.log('Hide more button');
document.getElementById('buttons').style.display = 'none';
}
जवाब में Link हेडर मौजूद है या नहीं, इसके आधार पर हम [More books...] बटन को दिखाएंगे या छिपाएंगे. ऐसा इसलिए, क्योंकि Link हेडर से हमें यह पता चलता है कि अभी और किताबें लोड होनी बाकी हैं या नहीं. अगर किताबें लोड होनी बाकी होंगी, तो Link हेडर में next यूआरएल मौजूद होगा.
const library = document.getElementById('library');
const template = document.getElementById('book-card');
for (let book of books) {
const bookCard = template.content.cloneNode(true);
bookCard.querySelector('slot[name=title]').innerText = book.title;
bookCard.querySelector('slot[name=language]').innerText = book.language;
bookCard.querySelector('slot[name=author]').innerText = book.author;
bookCard.querySelector('slot[name=year]').innerText = book.year;
bookCard.querySelector('slot[name=pages]').innerText = book.pages;
const img = document.createElement('img');
img.setAttribute('id', book.isbn);
img.setAttribute('class', 'img-barcode-' + book.isbn)
bookCard.querySelector('slot[name=barcode]').appendChild(img);
library.appendChild(bookCard);
...
}
}
फ़ंक्शन के ऊपर दिए गए सेक्शन में, REST API से मिली हर किताब के लिए, हम उस टेंप्लेट को क्लोन करने जा रहे हैं जिसमें किताब को दिखाने वाले कुछ वेब कॉम्पोनेंट मौजूद हैं. साथ ही, हम टेंप्लेट के स्लॉट में किताब की जानकारी भर रहे हैं.
JsBarcode('.img-barcode-' + book.isbn).EAN13(book.isbn, {fontSize: 18, textMargin: 0, height: 60}).render();
आईएसबीएन कोड को थोड़ा बेहतर बनाने के लिए, हम JsBarcode लाइब्रेरी का इस्तेमाल करते हैं. इससे हमें असली किताबों के बैक कवर पर मौजूद बारकोड जैसा बारकोड बनाने में मदद मिलती है!
ऐप्लिकेशन को स्थानीय तौर पर चलाना और उसकी जांच करना
अभी के लिए इतना कोड काफ़ी है. अब ऐप्लिकेशन को इस्तेमाल करने का समय है. सबसे पहले, हम इसे Cloud Shell में स्थानीय तौर पर लागू करेंगे. इसके बाद, इसे असल में डिप्लॉय किया जाएगा.
हम अपने ऐप्लिकेशन के लिए ज़रूरी NPM मॉड्यूल को इस कमांड की मदद से इंस्टॉल करते हैं:
$ npm install
इसके बाद, हम ऐप्लिकेशन को सामान्य तरीके से चलाते हैं:
$ npm start
इसके अलावा, nodemon की मदद से बदलावों को अपने-आप फिर से लोड किया जा सकता है. इसके लिए:
$ npm run dev
यह ऐप्लिकेशन लोकल तौर पर चल रहा है. इसे ब्राउज़र से http://localhost:8080 पर ऐक्सेस किया जा सकता है.
App Engine ऐप्लिकेशन को डिप्लॉय करना
अब हमें भरोसा है कि हमारा ऐप्लिकेशन स्थानीय तौर पर ठीक से काम कर रहा है. इसलिए, अब इसे App Engine पर डिप्लॉय करने का समय है.
ऐप्लिकेशन को डिप्लॉय करने के लिए, यह कमांड चलाएं:
$ gcloud app deploy -q
एक मिनट के बाद, ऐप्लिकेशन डिप्लॉय हो जाना चाहिए.
ऐप्लिकेशन, इस तरह के यूआरएल पर उपलब्ध होगा: https://${GOOGLE_CLOUD_PROJECT}.appspot.com.
App Engine वेब ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) के बारे में जानकारी
अब आप:
- ज़्यादा किताबें लोड करने के लिए,
[More books...]बटन पर क्लिक करें. - किसी भाषा को चुनकर, सिर्फ़ उस भाषा में मौजूद किताबें देखें.
- सभी किताबों की सूची पर वापस जाने के लिए, चुने गए बॉक्स में मौजूद छोटे क्रॉस पर क्लिक करके, चुने गए विकल्प को हटाया जा सकता है.
10. डेटा को व्यवस्थित करना (ज़रूरी नहीं)
अगर आपको ऐप्लिकेशन नहीं रखना है, तो लागत बचाने के लिए संसाधनों को क्लीन अप किया जा सकता है. साथ ही, पूरे प्रोजेक्ट को मिटाकर, क्लाउड का बेहतर तरीके से इस्तेमाल किया जा सकता है:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
11. बधाई हो!
हमने Cloud Functions, App Engine, और Cloud Run की मदद से, सेवाओं का एक सेट बनाया है. इससे अलग-अलग वेब एपीआई एंडपॉइंट और वेब फ़्रंटएंड को दिखाया जा सकता है. साथ ही, किताबों की लाइब्रेरी को सेव, अपडेट, और ब्राउज़ किया जा सकता है. इसके लिए, हमने REST API डेवलपमेंट के कुछ बेहतरीन डिज़ाइन पैटर्न का इस्तेमाल किया है.
हमने क्या-क्या बताया
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
ज़्यादा जानकारी
अगर आपको इस उदाहरण के बारे में ज़्यादा जानना है और इसे और बेहतर बनाना है, तो यहां दी गई सूची में शामिल चीज़ों के बारे में जानें:
- डेटा इंपोर्ट करने की सुविधा और REST API कंटेनर के लिए, एक सामान्य एपीआई फ़साड उपलब्ध कराने के लिए एपीआई गेटवे का इस्तेमाल करें. इससे एपीआई ऐक्सेस करने के लिए एपीआई कुंजियों को मैनेज करने या एपीआई का इस्तेमाल करने वालों के लिए दर की सीमाएं तय करने जैसी सुविधाएं जोड़ी जा सकती हैं.
- App Engine ऐप्लिकेशन में Swagger-UI नोड मॉड्यूल डिप्लॉय करें. इससे REST API के लिए दस्तावेज़ तैयार किए जा सकते हैं और टेस्ट प्लेग्राउंड उपलब्ध कराया जा सकता है.
- फ़्रंटएंड पर, ब्राउज़ करने की मौजूदा सुविधा के अलावा, डेटा में बदलाव करने और नई बुक एंट्री बनाने के लिए अतिरिक्त स्क्रीन जोड़ें. साथ ही, हम Cloud Firestore डेटाबेस का इस्तेमाल कर रहे हैं. इसलिए, इसकी रीयल-टाइम सुविधा का फ़ायदा उठाएं. इससे, बदलाव होने पर किताबों का डेटा अपडेट हो जाएगा.