1. Ringkasan
Tujuan codelab ini adalah untuk mendapatkan pengalaman dengan layanan "serverless" yang ditawarkan oleh Google Cloud Platform:
- Cloud Functions — untuk men-deploy unit kecil logika bisnis dalam bentuk fungsi, yang bereaksi terhadap berbagai peristiwa (pesan Pub/Sub, file baru di Cloud Storage, permintaan HTTP, dan lainnya),
- App Engine — untuk men-deploy dan menyajikan aplikasi web, API web, backend seluler, aset statis, dengan kemampuan penskalaan naik dan turun yang cepat,
- Cloud Run — untuk men-deploy dan menskalakan container, yang dapat berisi bahasa, runtime, atau library apa pun.
Selain itu, Anda akan mempelajari cara memanfaatkan layanan serverless tersebut untuk men-deploy dan menskalakan API Web dan REST, sekaligus melihat beberapa prinsip desain RESTful yang baik.
Dalam workshop ini, kita akan membuat penjelajah rak buku yang terdiri dari:
- Cloud Function: untuk mengimpor set data awal buku yang tersedia di perpustakaan kami, di database dokumen Cloud Firestore,
- Container Cloud Run: yang akan mengekspos REST API melalui konten database kita,
- Frontend web App Engine: untuk menjelajahi daftar buku, dengan memanggil REST API kami.
Berikut tampilan frontend web di akhir codelab ini:

Yang akan Anda pelajari
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
2. Penyiapan dan persyaratan
Penyiapan lingkungan mandiri
- Login ke Google Cloud Console dan buat project baru atau gunakan kembali project yang sudah ada. Jika belum memiliki akun Gmail atau Google Workspace, Anda harus membuatnya.



- Project name adalah nama tampilan untuk peserta project ini. String ini adalah string karakter yang tidak digunakan oleh Google API. Anda dapat memperbaruinya kapan saja.
- Project ID bersifat unik di semua project Google Cloud dan tidak dapat diubah (tidak dapat diubah setelah ditetapkan). Cloud Console otomatis membuat string unik; biasanya Anda tidak mementingkan kata-katanya. Di sebagian besar codelab, Anda harus merujuk Project ID-nya (umumnya diidentifikasi sebagai
PROJECT_ID). Jika tidak suka dengan ID yang dibuat, Anda dapat membuat ID acak lainnya. Atau, Anda dapat mencobanya sendiri, dan lihat apakah ID tersebut tersedia. ID tidak dapat diubah setelah langkah ini dan tersedia selama durasi project. - Sebagai informasi, ada nilai ketiga, Project Number, yang digunakan oleh beberapa API. Pelajari lebih lanjut ketiga nilai ini di dokumentasi.
- Selanjutnya, Anda harus mengaktifkan penagihan di Konsol Cloud untuk menggunakan resource/API Cloud. Menjalankan operasi dalam codelab ini tidak akan memakan banyak biaya, bahkan mungkin tidak sama sekali. Guna mematikan resource agar tidak menimbulkan penagihan di luar tutorial ini, Anda dapat menghapus resource yang dibuat atau menghapus project-nya. Pengguna baru Google Cloud memenuhi syarat untuk mengikuti program Uji Coba Gratis senilai $300 USD.
Mulai Cloud Shell
Meskipun Google Cloud dapat dioperasikan dari jarak jauh menggunakan laptop Anda, dalam codelab ini, Anda akan menggunakan Google Cloud Shell, lingkungan command line yang berjalan di Cloud.
Dari Google Cloud Console, klik ikon Cloud Shell di toolbar kanan atas:

Hanya perlu waktu beberapa saat untuk penyediaan dan terhubung ke lingkungan. Jika sudah selesai, Anda akan melihat tampilan seperti ini:

Mesin virtual ini berisi semua alat pengembangan yang Anda perlukan. Layanan ini menawarkan direktori beranda tetap sebesar 5 GB dan beroperasi di Google Cloud, sehingga sangat meningkatkan performa dan autentikasi jaringan. Semua pekerjaan Anda dalam codelab ini dapat dilakukan di browser. Anda tidak perlu menginstal apa pun.
3. Menyiapkan lingkungan dan mengaktifkan API cloud
Untuk menggunakan berbagai layanan yang akan kita butuhkan selama project ini, kita akan mengaktifkan beberapa API. Kita akan melakukannya dengan meluncurkan perintah berikut di Cloud Shell:
$ gcloud services enable \
appengine.googleapis.com \
cloudbuild.googleapis.com \
cloudfunctions.googleapis.com \
compute.googleapis.com \
firestore.googleapis.com \
run.googleapis.com
Setelah beberapa saat, Anda akan melihat operasi selesai dengan berhasil:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
Kita juga akan menyiapkan variabel lingkungan yang akan kita butuhkan di sepanjang proses: region cloud tempat kita akan men-deploy fungsi, aplikasi, dan container:
$ export REGION=europe-west3
Karena kita akan menyimpan data di database Cloud Firestore, kita perlu membuat database:
$ gcloud app create --region=${REGION}
$ gcloud firestore databases create --location=${REGION}
Nanti dalam codelab ini, saat menerapkan REST API, kita perlu mengurutkan dan memfilter data. Untuk tujuan tersebut, kita akan membuat tiga indeks:
$ 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
Ketiga indeks tersebut sesuai dengan penelusuran yang akan kita lakukan menurut penulis atau bahasa, sekaligus mempertahankan pengurutan dalam koleksi melalui kolom yang diperbarui.
4. Mendapatkan kode
Dapatkan kode dari repositori GitHub berikut:
$ git clone https://github.com/glaforge/serverless-web-apis
Kode aplikasi ditulis menggunakan Node.JS.
Anda akan memiliki struktur folder berikut yang relevan untuk lab ini:
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
Berikut adalah folder yang relevan:
data— Folder ini berisi contoh data daftar 100 buku.function-import— Fungsi ini akan menawarkan endpoint untuk mengimpor data sampel.run-crud— Penampung ini akan mengekspos Web API untuk mengakses data buku yang disimpan di Cloud Firestore.appengine-frontend— Aplikasi web App Engine ini akan menampilkan frontend hanya baca sederhana untuk menjelajahi daftar buku.
5. Data pustaka buku contoh
Di folder data, kita memiliki file books.json yang berisi daftar seratus buku, yang mungkin layak dibaca. Dokumen JSON ini adalah array yang berisi objek JSON. Mari kita lihat bentuk data yang akan kita masukkan melalui Cloud Function:
[
{
"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
},
...
]
Semua entri buku kami dalam array ini berisi informasi berikut:
isbn— Kode ISBN-13 yang mengidentifikasi buku.author— Nama penulis buku.language— Bahasa lisan yang digunakan dalam buku.pages— Jumlah halaman dalam buku.title— Judul buku.year— Tahun buku diterbitkan.
6. Endpoint fungsi untuk mengimpor data buku sampel
Di bagian pertama ini, kita akan mengimplementasikan endpoint yang akan digunakan untuk mengimpor data buku contoh. Kita akan menggunakan Cloud Functions untuk tujuan ini.
Mempelajari kode
Mari kita mulai dengan melihat file 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"
}
}
Dalam dependensi runtime, kita hanya memerlukan modul NPM @google-cloud/firestore untuk mengakses database dan menyimpan data buku. Di balik layar, runtime Cloud Functions juga menyediakan framework web Express, sehingga kita tidak perlu mendeklarasikannya sebagai dependensi.
Dalam dependensi pengembangan, kita mendeklarasikan Functions Framework (@google-cloud/functions-framework), yang merupakan framework runtime yang digunakan untuk memanggil fungsi Anda. Framework ini adalah framework open source yang juga dapat Anda gunakan secara lokal di mesin Anda (dalam kasus ini, di dalam Cloud Shell) untuk menjalankan fungsi tanpa men-deploy setiap kali Anda membuat perubahan, sehingga meningkatkan loop masukan pengembangan.
Untuk menginstal dependensi, gunakan perintah install:
$ npm install
Skrip start menggunakan Functions Framework untuk memberi Anda perintah yang dapat digunakan untuk menjalankan fungsi secara lokal dengan petunjuk berikut:
$ npm start
Anda dapat menggunakan curl atau berpotensi menggunakan pratinjau web Cloud Shell untuk permintaan HTTP GET guna berinteraksi dengan fungsi.
Sekarang, mari kita lihat file index.js yang berisi logika fungsi impor data buku:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
Kita membuat instance modul Firestore, dan mengarah ke koleksi buku (mirip dengan tabel dalam database relasional).
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;
}
...
})
Kita mengekspor fungsi JavaScript parseBooks. Ini adalah fungsi yang akan kita deklarasikan saat men-deploy-nya nanti.
Beberapa petunjuk berikutnya akan memeriksa bahwa:
- Kami hanya menerima permintaan HTTP
POST, dan jika tidak, kami akan menampilkan kode status405untuk menunjukkan bahwa metode HTTP lainnya tidak diizinkan. - Kami hanya menerima payload
application/json, dan jika tidak, kami akan mengirimkan kode status406untuk menunjukkan bahwa ini bukan format payload yang dapat diterima.
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()
});
}
Kemudian, kita dapat mengambil payload JSON melalui body permintaan. Kita sedang menyiapkan operasi batch Firestore untuk menyimpan semua buku secara massal. Kita melakukan iterasi pada array JSON yang terdiri dari detail buku, dengan memeriksa kolom isbn, title, author, language, pages, dan year. Kode ISBN buku akan berfungsi sebagai kunci utama atau ID-nya.
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"});
Setelah sebagian besar data siap, kita dapat melakukan operasi. Jika operasi penyimpanan gagal, kami akan menampilkan kode status 400 untuk menunjukkan bahwa operasi tersebut gagal. Jika tidak, kita dapat menampilkan respons OK, dengan kode status 202 yang menunjukkan bahwa permintaan penyimpanan massal diterima.
Menjalankan dan menguji fungsi impor
Sebelum menjalankan kode, kita akan menginstal dependensi dengan:
$ npm install
Untuk menjalankan fungsi secara lokal, berkat Functions Framework, kita akan menggunakan perintah skrip start yang kita tentukan di package.json:
$ npm start > start > npx @google-cloud/functions-framework --target=parseBooks Serving function... Function: parseBooks URL: http://localhost:8080/
Untuk mengirim permintaan HTTP POST ke fungsi lokal, Anda dapat menjalankan:
$ curl -d "@../data/books.json" \
-H "Content-Type: application/json" \
http://localhost:8080/
Saat meluncurkan perintah ini, Anda akan melihat output berikut, yang mengonfirmasi bahwa fungsi berjalan secara lokal:
{"status":"OK"}
Anda juga dapat membuka UI Cloud Console untuk memeriksa apakah data memang disimpan di Firestore:

Pada screenshot di atas, kita dapat melihat koleksi books yang dibuat, daftar dokumen buku yang diidentifikasi oleh kode ISBN buku, dan detail entri buku tertentu di sebelah kanan.
Men-deploy fungsi di cloud
Untuk men-deploy fungsi di Cloud Functions, kita akan menggunakan perintah berikut di direktori function-import:
$ gcloud functions deploy bulk-import \
--gen2 \
--trigger-http \
--runtime=nodejs20 \
--allow-unauthenticated \
--max-instances=30
--region=${REGION} \
--source=. \
--entry-point=parseBooks
Kita men-deploy fungsi dengan nama simbolis bulk-import. Fungsi ini dipicu melalui permintaan HTTP. Kami menggunakan runtime Node.JS 20. Kita men-deploy fungsi secara publik (sebaiknya, kita harus mengamankan endpoint tersebut). Kita menentukan region tempat kita ingin fungsi berada. Kemudian, kita mengarahkan ke sumber di direktori lokal dan menggunakan parseBooks (fungsi JavaScript yang diekspor) sebagai titik entri.
Setelah beberapa menit atau kurang, fungsi akan di-deploy di cloud. Di UI Konsol Cloud, Anda akan melihat fungsi muncul:

Di output deployment, Anda akan dapat melihat URL fungsi Anda, yang mengikuti konvensi penamaan tertentu (https://${REGION}-${GOOGLE_CLOUD_PROJECT}.cloudfunctions.net/${FUNCTION_NAME}), dan tentu saja, Anda juga dapat menemukan URL pemicu HTTP ini di UI Konsol Cloud, di tab pemicu:

Anda juga dapat mengambil URL melalui command line dengan gcloud:
$ export BULK_IMPORT_URL=$(gcloud functions describe bulk-import \
--region=$REGION \
--format 'value(httpsTrigger.url)')
$ echo $BULK_IMPORT_URL
Mari kita simpan di variabel lingkungan BULK_IMPORT_URL, sehingga kita dapat menggunakannya kembali untuk menguji fungsi yang di-deploy.
Menguji fungsi yang di-deploy
Dengan perintah curl serupa yang kita gunakan sebelumnya untuk menguji fungsi yang berjalan secara lokal, kita akan menguji fungsi yang di-deploy. Satu-satunya perubahan adalah URL:
$ curl -d "@../data/books.json" \
-H "Content-Type: application/json" \
$BULK_IMPORT_URL
Sekali lagi, jika berhasil, output berikut akan ditampilkan:
{"status":"OK"}
Setelah fungsi impor di-deploy dan siap, serta kita telah mengupload data contoh, sekarang saatnya mengembangkan REST API yang mengekspos set data ini.
7. Kontrak REST API
Meskipun kita tidak menentukan kontrak API menggunakan, misalnya, spesifikasi Open API, kita akan melihat berbagai endpoint REST API.
API ini menukar objek JSON buku, yang terdiri dari:
isbn(opsional) —String13 karakter yang merepresentasikan kode ISBN yang valid,author—Stringyang tidak kosong dan merepresentasikan nama penulis buku,language—Stringyang tidak kosong dan berisi bahasa yang digunakan untuk menulis buku,pages—Integerpositif untuk jumlah halaman buku,title—Stringyang tidak kosong dengan judul buku,year— nilaiIntegeruntuk tahun publikasi buku.
Contoh payload buku:
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
}
GET /books
Mendapatkan daftar semua buku, yang berpotensi difilter menurut penulis dan/atau bahasa, dan diatur dalam halaman dengan 10 hasil sekaligus.
Payload isi pesan: tidak ada.
Parameter kueri:
author(opsional) — memfilter daftar buku menurut penulis,language(opsional) — memfilter daftar buku menurut bahasa,page(opsional, default = 0) — menunjukkan peringkat halaman hasil yang akan ditampilkan.
Menampilkan: array JSON objek buku.
Kode status:
200— saat permintaan berhasil mengambil daftar buku,400— jika terjadi error.
POST /books dan POST /books/{isbn}
Memposting payload buku baru, baik dengan parameter jalur isbn (dalam hal ini kode isbn tidak diperlukan dalam payload buku) atau tanpa (dalam hal ini kode isbn harus ada dalam payload buku)
Payload isi: objek buku.
Parameter kueri: tidak ada.
Menampilkan: tidak ada.
Kode status:
201— saat buku berhasil disimpan,406— jika kodeisbntidak valid,400— jika terjadi error.
GET /books/{isbn}
Mengambil buku dari perpustakaan, yang diidentifikasi dengan kode isbn, yang diteruskan sebagai parameter jalur.
Payload isi pesan: tidak ada.
Parameter kueri: tidak ada.
Menampilkan: objek JSON buku, atau objek error jika buku tidak ada.
Kode status:
200— jika buku ditemukan dalam database,400— jika terjadi error,404— jika buku tidak dapat ditemukan,406— jika kodeisbntidak valid.
PUT /books/{isbn}
Memperbarui buku yang ada, yang diidentifikasi oleh isbn yang diteruskan sebagai parameter jalur.
Payload isi: objek buku. Hanya kolom yang perlu diperbarui yang dapat diteruskan, kolom lainnya bersifat opsional.
Parameter kueri: tidak ada.
Menampilkan: buku yang diperbarui.
Kode status:
200— saat buku berhasil diperbarui,400— jika terjadi error,406— jika kodeisbntidak valid.
DELETE /books/{isbn}
Menghapus buku yang ada, yang diidentifikasi oleh isbn-nya yang diteruskan sebagai parameter jalur.
Payload isi pesan: tidak ada.
Parameter kueri: tidak ada.
Menampilkan: tidak ada.
Kode status:
204— saat buku berhasil dihapus,400— jika terjadi error.
8. Men-deploy dan mengekspos REST API dalam container
Mempelajari kode
Dockerfile
Mari kita mulai dengan melihat Dockerfile, yang akan bertanggung jawab untuk membuat kode aplikasi kita dalam container:
FROM node:20-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . ./
CMD [ "node", "index.js" ]
Kita menggunakan image "slim" Node.JS 20. Kita akan bekerja di direktori /usr/src/app. Kita menyalin file package.json (detail di bawah) yang menentukan dependensi kita, dan hal-hal lainnya. Kita menginstal dependensi dengan npm install, menyalin kode sumber. Terakhir, kita menunjukkan cara menjalankan aplikasi ini, dengan perintah node index.js.
package.json
Selanjutnya, kita dapat melihat file 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"
}
}
Kita menentukan bahwa kita ingin menggunakan Node.JS 14, seperti yang terjadi pada Dockerfile.
Aplikasi web API kami bergantung pada:
- Modul NPM Firestore untuk mengakses data buku di database,
- Library
corsuntuk menangani permintaan CORS (Cross Origin Resource Sharing), karena REST API kami akan dipanggil dari kode klien frontend aplikasi web App Engine kami, - Framework Express, yang akan menjadi framework web kita untuk mendesain API,
- Kemudian, modul
isbn3yang membantu memvalidasi kode ISBN buku.
Kita juga menentukan skrip start, yang akan berguna untuk memulai aplikasi secara lokal, untuk tujuan pengembangan dan pengujian.
index.js
Mari kita lanjutkan ke bagian inti kode, dengan melihat index.js secara mendalam:
const Firestore = require('@google-cloud/firestore');
const firestore = new Firestore();
const bookStore = firestore.collection('books');
Kita memerlukan modul Firestore, dan mereferensikan koleksi books, tempat data buku kita disimpan.
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'],
}));
Kita menggunakan Express sebagai framework web untuk menerapkan REST API. Kita menggunakan modul body-parser untuk mengurai payload JSON yang dipertukarkan dengan API kita.
Modul querystring berguna untuk memanipulasi URL. Hal ini akan terjadi saat kita membuat header Link untuk tujuan penomoran halaman (akan dibahas lebih lanjut nanti).
Kemudian, kita mengonfigurasi modul cors. Kita secara eksplisit menentukan header yang ingin kita teruskan melalui CORS, karena sebagian besar biasanya dihapus, tetapi di sini, kita ingin mempertahankan panjang dan jenis konten biasa, serta header Link yang akan kita tentukan untuk penomoran halaman.
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;
}
Kita akan menggunakan modul NPM isbn3 untuk mengurai dan memvalidasi kode ISBN, serta mengembangkan fungsi utilitas kecil yang akan mengurai kode ISBN, dan merespons dengan kode status 406 pada respons, jika kode ISBN tidak valid.
GET /books
Mari kita lihat endpoint GET /books, bagian demi bagian:
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}`});
}
});
Kita akan siap mengkueri database dengan menyiapkan kueri. Kueri ini akan bergantung pada parameter kueri opsional, untuk memfilter menurut penulis dan/atau menurut bahasa. Kita juga menampilkan daftar buku dalam kelompok 10 buku.
Jika terjadi error saat mengambil buku, kami akan menampilkan error dengan kode status 400.
Mari kita perbesar bagian endpoint yang dipotong:
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);
});
}
Di bagian sebelumnya, kita memfilter menurut author dan language, tetapi di bagian ini, kita akan mengurutkan daftar buku berdasarkan urutan tanggal terakhir diperbarui (yang terakhir diperbarui muncul pertama). Kita juga akan melakukan penomoran halaman pada hasilnya, dengan menentukan batas (jumlah elemen yang akan ditampilkan), dan offset (titik awal dari mana batch buku berikutnya akan ditampilkan).
Kita menjalankan kueri, mendapatkan snapshot data, dan menempatkan hasil tersebut dalam array JavaScript yang akan ditampilkan di akhir fungsi.
Mari selesaikan penjelasan tentang endpoint ini dengan melihat praktik terbaik: menggunakan header Link untuk menentukan link URI ke halaman data pertama, sebelumnya, berikutnya, atau terakhir (dalam kasus ini, kita hanya akan memberikan halaman sebelumnya dan berikutnya).
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);
Logikanya mungkin tampak sedikit rumit di sini pada awalnya, tetapi yang kita lakukan adalah menambahkan link previous jika kita tidak berada di halaman pertama data. Kita menambahkan link next jika halaman data sudah penuh (yaitu berisi jumlah maksimum buku seperti yang ditentukan oleh konstanta PAGE_SIZE, dengan asumsi ada halaman lain yang akan muncul dengan lebih banyak data). Kemudian, kita menggunakan fungsi resource#links() Express untuk membuat header yang tepat dengan sintaksis yang benar.
Sebagai informasi, header link akan terlihat seperti ini:
link: </books?page=1>; rel="prev", </books?page=3>; rel="next"
POST /booksdanPOST /books/:isbn
Kedua endpoint ini digunakan untuk membuat buku baru. Salah satunya meneruskan kode ISBN dalam payload buku, sedangkan yang lainnya meneruskannya sebagai parameter jalur. Bagaimanapun juga, keduanya memanggil fungsi createBook() kita:
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}`});
}
}
Kami memeriksa apakah kode isbn valid, jika tidak, keluar dari fungsi (dan menetapkan kode status 406). Kita mengambil kolom buku dari payload yang diteruskan di isi permintaan. Kemudian, kita akan menyimpan detail buku di Firestore. Menampilkan 201 jika berhasil, dan 400 jika gagal.
Saat berhasil ditampilkan, kami juga menyetel header lokasi, sehingga memberikan petunjuk kepada klien API tentang lokasi resource yang baru dibuat. Header akan terlihat sebagai berikut:
Location: /books/9781234567898
GET /books/:isbn
Mari kita ambil buku, yang diidentifikasi melalui ISBN-nya, dari Firestore.
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}`});
}
});
Seperti biasa, kami memeriksa apakah ISBN valid. Kita membuat kueri ke Firestore untuk mengambil buku. Properti snapshot.exists berguna untuk mengetahui apakah buku ditemukan atau tidak. Jika tidak, kami akan mengirimkan kembali error dan kode status 404 Not Found. Kita mengambil data buku, dan membuat objek JSON yang merepresentasikan buku, untuk ditampilkan.
PUT /books/:isbn
Kita menggunakan metode PUT untuk memperbarui buku yang ada.
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}`});
}
});
Kami memperbarui kolom tanggal/waktu updated untuk mengingat kapan terakhir kali kami memperbarui data tersebut. Kami menggunakan strategi {merge:true} yang menggantikan kolom yang ada dengan nilai barunya (jika tidak, semua kolom akan dihapus, dan hanya kolom baru dalam payload yang akan disimpan, sehingga menghapus kolom yang ada dari pembaruan sebelumnya atau pembuatan awal).
Kami juga menetapkan header Location untuk mengarah ke URI buku.
DELETE /books/:isbn
Menghapus buku cukup mudah. Kita hanya memanggil metode delete() pada referensi dokumen. Kami menampilkan kode status 204 karena kami tidak menampilkan konten apa pun.
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}`});
}
});
Mulai server Express / Node
Terakhir, kita memulai server, yang memproses port 8080 secara default:
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Books Web API service: listening on port ${port}`);
console.log(`Node ${process.version}`);
});
Menjalankan aplikasi secara lokal
Untuk menjalankan aplikasi secara lokal, kita akan menginstal dependensi terlebih dahulu dengan:
$ npm install
Kemudian kita bisa memulai dengan:
$ npm start
Server akan dimulai di localhost dan memproses di port 8080 secara default.
Anda juga dapat membangun container Docker, dan menjalankan image container, dengan perintah berikut:
$ docker build -t crud-web-api . $ docker run --rm -p 8080:8080 -it crud-web-api
Menjalankan aplikasi dalam Docker juga merupakan cara yang tepat untuk memeriksa kembali bahwa containerisasi aplikasi kita akan berjalan dengan baik saat kita membangunnya di cloud dengan Cloud Build.
Menguji API
Terlepas dari cara kita menjalankan kode REST API (secara langsung melalui Node atau melalui image container Docker), kita sekarang dapat menjalankan beberapa kueri terhadapnya.
- Buat buku baru (ISBN di payload isi):
$ curl -XPOST -d '{"isbn":"9782070368228","title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books
- Membuat buku baru (ISBN dalam parameter jalur):
$ curl -XPOST -d '{"title":"Book","author":"me","pages":123,"year":2021,"language":"French"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books/9782070368228
- Hapus buku (yang kita buat):
$ curl -XDELETE http://localhost:8080/books/9782070368228
- Mendapatkan buku berdasarkan ISBN:
$ curl http://localhost:8080/books/9780140449136 $ curl http://localhost:8080/books/9782070360536
- Perbarui buku yang ada dengan hanya mengubah judulnya:
$ curl -XPUT \
-d '{"title":"Book"}' \
-H "Content-Type: application/json" \
http://localhost:8080/books/9780003701203
- Ambil daftar buku (10 buku pertama):
$ curl http://localhost:8080/books
- Menemukan buku yang ditulis oleh penulis tertentu:
$ curl http://localhost:8080/books?author=Virginia+Woolf
- Cantumkan buku yang ditulis dalam bahasa Inggris:
$ curl http://localhost:8080/books?language=English
- Muat halaman ke-4 buku:
$ curl http://localhost:8080/books?page=3
Kita juga dapat menggabungkan parameter kueri author, language, dan books untuk menyaring penelusuran.
Membangun dan men-deploy REST API yang di-containerkan
Karena REST API berfungsi sesuai rencana, sekarang saat yang tepat untuk men-deploy-nya di Cloud, di Cloud Run.
Kita akan melakukannya dalam dua langkah:
- Pertama, dengan membangun image container menggunakan Cloud Build, dengan perintah berikut:
$ gcloud builds submit \
--tag gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api
- Kemudian, dengan men-deploy layanan menggunakan perintah kedua ini:
$ gcloud run deploy run-crud \
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api \
--allow-unauthenticated \
--region=${REGION} \
--platform=managed
Dengan perintah pertama, Cloud Build akan membangun image container dan menghostingnya di Container Registry. Perintah berikutnya men-deploy image container dari registry, dan men-deploy-nya di region cloud.
Kita dapat memeriksa ulang di UI Konsol Cloud bahwa layanan Cloud Run kita kini muncul dalam daftar:

Langkah terakhir yang akan kita lakukan di sini adalah mengambil URL layanan Cloud Run yang baru di-deploy, berkat perintah berikut:
$ export RUN_CRUD_SERVICE_URL=$(gcloud run services describe run-crud \
--region=${REGION} \
--platform=managed \
--format='value(status.url)')
Kita akan memerlukan URL Cloud Run REST API di bagian berikutnya, karena kode frontend App Engine akan berinteraksi dengan API.
9. Menghosting aplikasi web untuk menjelajahi pustaka
Bagian terakhir dari teka-teki untuk menambahkan kilau pada project ini adalah menyediakan frontend web yang akan berinteraksi dengan REST API kita. Untuk tujuan tersebut, kita akan menggunakan Google App Engine, dengan beberapa kode JavaScript klien yang akan memanggil API melalui permintaan AJAX (menggunakan Fetch API sisi klien).
Meskipun di-deploy di runtime Node.JS App Engine, aplikasi kita sebagian besar terdiri dari resource statis. Tidak ada banyak kode backend, karena sebagian besar interaksi pengguna akan dilakukan di browser melalui JavaScript sisi klien. Kita tidak akan menggunakan framework JavaScript frontend yang canggih, kita hanya akan menggunakan beberapa JavaScript "vanilla", dengan beberapa Komponen Web untuk UI menggunakan library komponen web Shoelace:
- kotak pilihan untuk memilih bahasa buku:

- komponen kartu untuk menampilkan detail tentang buku tertentu (termasuk kode batang untuk merepresentasikan ISBN buku, menggunakan library JsBarcode):

- dan tombol untuk memuat lebih banyak buku dari database:

Saat menggabungkan semua komponen visual tersebut, halaman web yang dihasilkan untuk menjelajahi pustaka kami akan terlihat sebagai berikut:

app.yaml file konfigurasi
Mari kita mulai mempelajari codebase aplikasi App Engine ini dengan melihat file konfigurasi app.yaml-nya. File ini khusus untuk App Engine, dan memungkinkan konfigurasi seperti variabel lingkungan, berbagai "handler" aplikasi, atau menentukan bahwa beberapa resource adalah aset statis, yang akan ditayangkan oleh CDN bawaan 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
Kita menentukan bahwa aplikasi kita adalah aplikasi Node.JS, dan kita ingin menggunakan versi 14.
Kemudian, kita menentukan variabel lingkungan yang mengarah ke URL layanan Cloud Run. Kita harus memperbarui placeholder CHANGE_ME dengan URL yang benar (lihat di bawah tentang cara mengubahnya).
Setelah itu, kita menentukan berbagai handler. 3 yang pertama mengarah ke lokasi kode sisi klien HTML, CSS, dan JavaScript, di folder public/ dan subfoldernya. Yang keempat menunjukkan bahwa URL root aplikasi App Engine kita harus mengarah ke halaman index.html. Dengan begitu, kita tidak akan melihat akhiran index.html di URL saat mengakses root situs. Yang terakhir adalah default yang akan merutekan semua URL lainnya (/.*) ke aplikasi Node.JS kita (yaitu bagian "dynamic" dari aplikasi, berbeda dengan aset statis yang telah kita jelaskan).
Sekarang, mari perbarui URL Web API layanan Cloud Run.
Di direktori appengine-frontend/, jalankan perintah berikut untuk memperbarui variabel lingkungan yang mengarah ke URL REST API berbasis Cloud Run:
$ sed -i -e "s|CHANGE_ME|${RUN_CRUD_SERVICE_URL}|" app.yaml
Atau ubah string CHANGE_ME di app.yaml secara manual dengan URL yang benar:
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
package.json File Node.JS
{
"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"
}
}
Kami tekankan lagi bahwa kami ingin menjalankan aplikasi ini menggunakan Node.JS 14. Kita bergantung pada framework Express, serta modul NPM isbn3 untuk memvalidasi kode ISBN buku.
Dalam dependensi pengembangan, kita akan menggunakan modul nodemon untuk memantau perubahan file. Meskipun kita dapat menjalankan aplikasi secara lokal dengan npm start, membuat beberapa perubahan pada kode, menghentikan aplikasi dengan ^C, lalu meluncurkannya kembali, hal ini agak membosankan. Sebagai gantinya, kita dapat menggunakan perintah berikut agar aplikasi dimuat ulang / dimulai ulang secara otomatis saat ada perubahan:
$ npm run dev
index.js Kode Node.JS
const express = require('express');
const app = express();
app.use(express.static('public'));
const bodyParser = require('body-parser');
app.use(bodyParser.json());
Kita memerlukan framework web Express. Kita menentukan bahwa direktori publik berisi aset statis yang dapat ditayangkan (setidaknya saat berjalan secara lokal dalam mode pengembangan) oleh middleware static. Terakhir, kita memerlukan body-parser untuk mengurai payload JSON.
Mari kita lihat beberapa rute yang telah kita tentukan:
app.get('/', async (req, res) => {
res.redirect('/html/index.html');
});
app.get('/webapi', async (req, res) => {
res.send(process.env.RUN_CRUD_SERVICE_URL);
});
Yang pertama yang cocok dengan / akan dialihkan ke index.html di direktori public/html kita. Karena dalam mode pengembangan kita tidak berjalan dalam runtime App Engine, kita tidak mendapatkan perutean URL App Engine. Jadi, di sini, kita hanya mengalihkan URL root ke file HTML.
Endpoint kedua yang kita tentukan /webapi akan menampilkan URL Cloud RUN REST API kita. Dengan begitu, kode JavaScript sisi klien akan mengetahui tempat untuk memanggil guna mendapatkan daftar buku.
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}`);
});
Terakhir, kita menjalankan aplikasi web Express dan memproses di port 8080 secara default.
index.html halaman
Kita tidak akan melihat setiap baris halaman HTML yang panjang ini. Sebagai gantinya, mari kita soroti beberapa baris penting.
<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">
Dua baris pertama mengimpor library komponen web Shoelace (skrip dan stylesheet).
Baris berikutnya mengimpor library JsBarcode, untuk membuat kode batang dari kode ISBN buku.
Baris terakhir mengimpor kode JavaScript dan stylesheet CSS kita sendiri, yang berada di subdirektori public/.
Di body halaman HTML, kita menggunakan komponen Shoelace dengan tag elemen kustomnya, seperti:
<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>
...
Kami juga menggunakan template HTML dan kemampuan pengisian slotnya untuk merepresentasikan buku. Kita akan membuat salinan template tersebut untuk mengisi daftar buku, dan mengganti nilai dalam slot dengan detail buku:
<template id="book-card">
<sl-card class="card-overview">
...
<slot name="author">Author</slot>
...
</sl-card>
</template>
Cukup HTML, kita hampir selesai meninjau kode. Bagian terakhir yang penting: kode JavaScript sisi klien app.js yang berinteraksi dengan REST API kita.
Kode JavaScript sisi klien app.js
Kita mulai dengan pemroses peristiwa tingkat teratas yang menunggu konten DOM dimuat:
document.addEventListener("DOMContentLoaded", async function(event) {
...
}
Setelah siap, kita dapat menyiapkan beberapa konstanta dan variabel utama:
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 = '';
Pertama, kita akan mengambil URL REST API, berkat kode node App Engine yang menampilkan variabel lingkungan yang kita tetapkan awalnya di app.yaml. Berkat variabel lingkungan, endpoint /webapi, yang dipanggil dari kode sisi klien JavaScript, kita tidak perlu meng-hardcode URL REST API dalam kode frontend.
Kita juga menentukan variabel page dan language, yang akan kita gunakan untuk melacak penomoran halaman dan pemfilteran bahasa.
const moreButton = document.getElementById('more-button');
moreButton.addEventListener('sl-focus', event => {
console.log('Button clicked');
moreButton.blur();
appendMoreBooks(server, page++, language);
});
Kita menambahkan pengendali peristiwa pada tombol untuk memuat buku. Saat diklik, tombol akan memanggil fungsi 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);
});
Hal serupa untuk kotak pilihan, kita menambahkan pengendali peristiwa untuk diberi tahu tentang perubahan pada pilihan bahasa. Seperti tombol, kita juga memanggil fungsi appendMoreBooks(), meneruskan URL REST API, halaman saat ini, dan pilihan bahasa.
Jadi, mari kita lihat fungsi yang mengambil dan menambahkan buku:
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();
...
}
Di atas, kita membuat URL yang tepat untuk digunakan dalam memanggil REST API. Biasanya ada tiga parameter kueri yang dapat kita tentukan, tetapi di UI ini, kita hanya menentukan dua parameter:
page— bilangan bulat yang menunjukkan halaman saat ini untuk penomoran halaman buku,language— string bahasa untuk memfilter menurut bahasa tulisan.
Kemudian, kita menggunakan Fetch API untuk mengambil array JSON yang berisi detail buku kita.
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';
}
Bergantung pada apakah header Link ada dalam respons, kami akan menampilkan atau menyembunyikan tombol [More books...], karena header Link adalah petunjuk yang memberi tahu kami apakah ada buku lain yang masih perlu dimuat (akan ada URL next di header Link).
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);
...
}
}
Di bagian fungsi di atas, untuk setiap buku yang ditampilkan oleh REST API, kita akan meng-clone template dengan beberapa komponen web yang merepresentasikan buku, dan kita akan mengisi slot template dengan detail buku.
JsBarcode('.img-barcode-' + book.isbn).EAN13(book.isbn, {fontSize: 18, textMargin: 0, height: 60}).render();
Untuk membuat kode ISBN terlihat lebih bagus, kita menggunakan library JsBarcode untuk membuat kode batang yang bagus seperti pada sampul belakang buku sungguhan.
Menjalankan dan menguji aplikasi secara lokal
Cukup kode untuk saat ini, saatnya melihat cara kerja aplikasi. Pertama, kita akan melakukannya secara lokal, di dalam Cloud Shell, sebelum men-deploy secara nyata.
Kita menginstal modul NPM yang diperlukan oleh aplikasi kita dengan:
$ npm install
Kemudian, kita menjalankan aplikasi dengan cara biasa:
$ npm start
Atau dengan memuat ulang perubahan secara otomatis berkat nodemon, dengan:
$ npm run dev
Aplikasi berjalan secara lokal, dan kita dapat mengaksesnya dari browser, di http://localhost:8080.
Men-deploy aplikasi App Engine
Setelah yakin bahwa aplikasi kita berjalan dengan baik secara lokal, sekarang saatnya men-deploy-nya di App Engine.
Untuk men-deploy aplikasi, jalankan perintah berikut:
$ gcloud app deploy -q
Setelah sekitar satu menit, aplikasi akan di-deploy.
Aplikasi akan tersedia di URL dengan bentuk: https://${GOOGLE_CLOUD_PROJECT}.appspot.com.
Menjelajahi UI aplikasi web App Engine
Sekarang Anda dapat:
- Klik tombol
[More books...]untuk memuat buku lainnya. - Pilih bahasa tertentu untuk melihat buku hanya dalam bahasa tersebut.
- Anda dapat menghapus pilihan dengan tanda silang kecil di kotak pilihan, untuk kembali ke daftar semua buku.
10. Membersihkan (opsional)
Jika tidak ingin menyimpan aplikasi, Anda dapat membersihkan resource untuk menghemat biaya dan menjadi pengguna cloud yang baik secara keseluruhan dengan menghapus seluruh project:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
11. Selamat!
Kami membuat serangkaian layanan, berkat Cloud Functions, App Engine, dan Cloud Run, untuk mengekspos berbagai endpoint Web API dan frontend web, untuk menyimpan, memperbarui, dan menjelajahi koleksi buku, dengan mengikuti beberapa pola desain yang baik untuk pengembangan REST API.
Yang telah kita bahas
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
Mempelajari lebih lanjut
Jika Anda ingin mempelajari lebih lanjut contoh konkret ini dan memperluasnya, berikut adalah daftar hal yang mungkin ingin Anda selidiki:
- Manfaatkan API Gateway untuk menyediakan fasad API umum ke fungsi impor data dan penampung REST API, untuk menambahkan fitur seperti penanganan kunci API untuk mengakses API, atau menentukan batasan kecepatan untuk konsumen API.
- Deploy modul node Swagger-UI di aplikasi App Engine untuk mendokumentasikan dan menawarkan platform pengujian untuk REST API.
- Di frontend, selain kemampuan penjelajahan yang ada, tambahkan layar tambahan untuk mengedit data, membuat entri buku baru. Selain itu, karena kita menggunakan database Cloud Firestore, manfaatkan fitur real-time-nya untuk memperbarui data buku yang ditampilkan saat perubahan dilakukan.