1. Ringkasan
Tujuan codelab ini adalah untuk mendapatkan pengalaman dengan "serverless" layanan 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 menyalurkan aplikasi web, API web, backend seluler, aset statis, dengan kemampuan peningkatan dan penurunan skala yang cepat,
- Cloud Run — untuk men-deploy dan menskalakan container, yang dapat berisi bahasa, runtime, atau library apa pun.
Dan untuk menemukan cara memanfaatkan layanan tanpa server tersebut untuk men-deploy dan menskalakan Web dan REST API, sambil melihat beberapa prinsip desain RESTful yang baik selama prosesnya.
Dalam workshop ini, kami akan membuat penjelajah rak buku yang terdiri dari:
- Cloud Function: untuk mengimpor set data awal buku yang tersedia di koleksi kami, dalam database dokumen Cloud Firestore,
- Container Cloud Run: yang akan mengekspos REST API atas konten database kita,
- Frontend web App Engine: untuk menelusuri daftar buku, dengan memanggil REST API.
Seperti inilah 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 Cloud API
Untuk menggunakan berbagai layanan yang akan kita perlukan selama project ini, kita akan mengaktifkan beberapa API. Untuk melakukannya, jalankan 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 waktu, Anda akan melihat operasi berhasil diselesaikan:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
Kita juga akan menyiapkan variabel lingkungan yang kita perlukan selama 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}
Selanjutnya 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 berdasarkan penulis atau bahasa, sambil mempertahankan urutan 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 data sampel 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 yang sederhana untuk menjelajahi daftar buku.
5. Contoh data koleksi buku
Dalam 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 serap 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 dalam array ini berisi informasi berikut:
isbn
— Kode ISBN-13 yang mengidentifikasi buku.author
— Nama penulis buku.language
— Bahasa lisan yang digunakan untuk menulis buku.pages
— Jumlah halaman dalam buku.title
— Judul buku.year
— Tahun buku diterbitkan.
6. Endpoint fungsi untuk mengimpor contoh data buku
Di bagian pertama ini, kita akan mengimplementasikan endpoint yang akan digunakan untuk mengimpor data buku sampel. 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, kami mendeklarasikan Functions Framework (@google-cloud/functions-framework
), yang merupakan framework runtime yang digunakan untuk memanggil fungsi Anda. Ini adalah framework open source yang juga dapat digunakan secara lokal di mesin Anda (dalam kasus kami, di dalam Cloud Shell) untuk menjalankan fungsi tanpa men-deploy setiap kali Anda melakukan perubahan, sehingga meningkatkan feedback loop pengembangan.
Untuk menginstal dependensi, gunakan perintah install
:
$ npm install
Skrip start
menggunakan Framework Functions untuk memberi Anda perintah yang dapat digunakan untuk menjalankan fungsi secara lokal dengan petunjuk berikut:
$ npm start
Anda dapat menggunakan curl atau mungkin pratinjau web Cloud Shell untuk permintaan GET HTTP agar dapat 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 mengarahkan 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;
}
...
})
Kami 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
POST
HTTP, dan menampilkan kode status405
untuk menunjukkan bahwa metode HTTP lainnya tidak diizinkan. - Kami hanya menerima payload
application/json
. Jika tidak, kami akan mengirim kode status406
untuk 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. Kami sedang menyiapkan operasi batch Firestore, untuk menyimpan semua buku secara massal. Kita melakukan iterasi melalui array JSON yang terdiri dari detail buku, melalui kolom isbn
, title
, author
, language
, pages
, dan year
. Kode ISBN buku akan berfungsi sebagai kunci utama atau ID.
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 sejumlah besar data siap, kita dapat melakukan operasi. Jika operasi penyimpanan gagal, kami akan menampilkan kode status 400
untuk memberi tahu bahwa operasi tersebut gagal. Jika tidak, kita dapat menampilkan respons OK, dengan kode status 202
yang menunjukkan bahwa permintaan penyimpanan massal telah diterima.
Menjalankan dan menguji fungsi impor
Sebelum menjalankan kode, kita akan menginstal dependensi dengan:
$ npm install
Untuk menjalankan fungsi secara lokal, berkat Framework Functions, kita akan menggunakan perintah skrip start
yang telah ditentukan di package.json
:
$ npm start > start > npx @google-cloud/functions-framework --target=parseBooks Serving function... Function: parseBooks URL: http://localhost:8080/
Untuk mengirim permintaan POST
HTTP 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 benar disimpan di Firestore:
Dalam screenshot di atas, kita dapat melihat koleksi books
yang dibuat, daftar dokumen buku yang diidentifikasi oleh kode ISBN buku, dan detail entri buku tertentu tersebut 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. Kita menggunakan runtime Node.JS 20. Kita men-deploy fungsi secara publik (idealnya, kita harus mengamankan endpoint tersebut). Kita menentukan region tempat kita ingin fungsi berada. Lalu, kita menunjuk sumber di direktori lokal dan menggunakan parseBooks
(fungsi JavaScript yang diekspor) sebagai titik entri.
Setelah beberapa menit atau kurang, fungsi tersebut akan di-deploy di cloud. Di UI Konsol Cloud, Anda akan melihat fungsi tersebut muncul:
Dalam 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 Cloud Console, 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
, agar 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, setelah mengupload data sampel, 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.
Pertukaran API memesan objek JSON, yang terdiri dari:
isbn
(opsional) —String
13 karakter yang mewakili kode ISBN yang valid,author
—String
yang tidak kosong yang mewakili nama penulis buku,language
—String
tidak kosong yang berisi bahasa penulisan buku,pages
—Integer
positif untuk jumlah halaman buku,title
—String
yang tidak kosong dengan judul buku,year
— nilaiInteger
untuk tahun penerbitan buku.
Contoh payload buku:
{
"isbn": "9780435272463",
"author": "Chinua Achebe",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
}
DAPATKAN /book
Dapatkan daftar semua buku, yang mungkin difilter menurut penulis dan/atau bahasa, dan diberi nomor halaman oleh jendela 10 hasil dalam satu waktu.
Payload isi: tidak ada.
Parameter kueri:
author
(opsional) — memfilter daftar buku berdasarkan penulis,language
(opsional) — memfilter daftar buku berdasarkan 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.
POSTINGAN /buku dan POSTINGAN /books/{isbn}
Posting payload buku baru, baik dengan parameter jalur isbn
(yang dalam hal ini kode isbn
tidak diperlukan dalam payload buku) maupun 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
— jika buku berhasil disimpan,406
— jika kodeisbn
tidak valid,400
— jika terjadi error.
DAPATKAN /books/{isbn}
Mengambil buku dari koleksi, yang diidentifikasi berdasarkan kode isbn
buku tersebut, yang diteruskan sebagai parameter jalur.
Payload isi: 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 kodeisbn
tidak valid.
PUT /books/{isbn}
Memperbarui buku yang ada, yang diidentifikasi melalui isbn
buku tersebut yang diteruskan sebagai parameter jalur.
Payload isi: objek buku. Hanya kolom yang memerlukan update yang dapat diteruskan, sedangkan kolom lainnya bersifat opsional.
Parameter kueri: tidak ada.
Pengembalian: buku yang diperbarui.
Kode status:
200
— saat buku berhasil diperbarui,400
— jika terjadi error,406
— jika kodeisbn
tidak valid.
HAPUS /buku/{isbn}
Menghapus buku yang ada, yang diidentifikasi dengan isbn
buku tersebut yang diteruskan sebagai parameter jalur.
Payload isi: 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 memasukkan kode aplikasi 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 menggunakan direktori /usr/src/app
. Kami menyalin file package.json
(detail di bawah) yang menentukan dependensi kami, di antara hal-hal lain. Kita menginstal dependensi dengan npm install
, sehingga menyalin kode sumber. Terakhir, kami menunjukkan cara aplikasi ini dijalankan, 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 dengan Dockerfile
.
Aplikasi API web kami bergantung pada:
- Modul NPM Firestore untuk mengakses data buku di database,
- Library
cors
untuk menangani permintaan CORS (Cross Origin Resource Sharing), karena REST API akan dipanggil dari kode klien frontend aplikasi web App Engine, - Framework Express, yang akan menjadi framework web untuk mendesain API,
- Kemudian, modul
isbn3
yang membantu memvalidasi kode ISBN buku.
Kami juga menentukan skrip start
, yang akan berguna untuk memulai aplikasi secara lokal, untuk tujuan pengembangan dan pengujian.
index.js
Mari kita beralih ke inti kode, dengan pembahasan mendalam tentang index.js
:
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 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'],
}));
Kami 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
sangat membantu untuk memanipulasi URL. Hal ini akan terjadi saat kita membuat header Link
untuk tujuan penomoran halaman (selengkapnya tentang hal ini akan dibahas nanti).
Kemudian, kita mengonfigurasi modul cors
. Kita eksplisit header yang ingin diteruskan melalui CORS, karena sebagian besar biasanya dihapus, tetapi di sini, kita ingin mempertahankan panjang dan jenis konten seperti biasa, serta header Link
yang akan ditentukan 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;
}
Kami akan menggunakan modul NPM isbn3
untuk mengurai dan memvalidasi kode ISBN, dan kami 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 bersiap untuk melakukan kueri {i>database<i}, dengan menyiapkan sebuah kueri. Kueri ini akan bergantung pada parameter kueri opsional, untuk memfilter menurut penulis dan/atau menurut bahasa. Kami juga mengembalikan daftar buku berdasarkan potongan 10 buku.
Jika terjadi error selama mengambil buku, kami akan menampilkan error dengan kode status 400.
Mari kita bahas bagian yang terpotong dari endpoint tersebut:
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 (terakhir diperbarui lebih dahulu). Kita juga akan memberi nomor halaman hasilnya, dengan menentukan batas (jumlah elemen yang akan ditampilkan), dan offset (titik awal untuk menampilkan kumpulan buku berikutnya).
Kita mengeksekusi kueri, mendapatkan snapshot data, dan memasukkan hasil tersebut dalam array JavaScript yang akan ditampilkan di akhir fungsi.
Mari kita selesaikan penjelasan endpoint ini, dengan melihat praktik yang baik: menggunakan header Link
untuk menentukan link URI ke halaman data pertama, sebelumnya, berikutnya, atau terakhir (dalam kasus ini, kami hanya akan memberikan 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 agak rumit di sini pada awalnya, tetapi yang kita lakukan adalah menambahkan link sebelumnya jika kita tidak berada di halaman pertama data. Dan kita menambahkan link berikutnya jika halaman data penuh (yaitu berisi jumlah buku maksimum seperti yang ditentukan oleh konstanta PAGE_SIZE
, dengan asumsi ada buku lain yang menghasilkan lebih banyak data). Kemudian, kita menggunakan fungsi resource#links()
dari Express untuk membuat header yang tepat dengan sintaksis yang tepat.
Sebagai informasi, header link akan terlihat seperti ini:
link: </books?page=1>; rel="prev", </books?page=3>; rel="next"
POST /books
danPOST /books/:isbn
Kedua endpoint siap untuk membuat buku baru. Satu meneruskan kode ISBN dalam payload buku, sedangkan yang lain meneruskan kode tersebut sebagai parameter jalur. Apa pun itu, keduanya panggil 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 akan memeriksa bahwa kode isbn
valid, atau kembali dari fungsi (dan menyetel kode status 406
). Kami mengambil kolom buku dari payload yang diteruskan dalam isi permintaan. Lalu, kita akan menyimpan detail buku di Firestore. Menampilkan 201
jika berhasil, dan 400
jika gagal.
Saat berhasil ditampilkan, kita juga akan menyetel header lokasi, sehingga dapat memberikan petunjuk kepada klien API tempat resource yang baru dibuat berada. {i>Header<i} akan terlihat seperti 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 akan memeriksa apakah ISBN valid. Kita membuat kueri ke Firestore untuk mengambil buku. Properti snapshot.exists
dapat membantu untuk mengetahui apakah buku telah ditemukan. Jika tidak, kami akan mengirim pesan error dan kode status 404
Tidak Ditemukan. Kita mengambil data buku, dan membuat objek JSON yang mewakili buku, yang akan ditampilkan.
PUT /books/:isbn
Kami 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 kami terakhir memperbarui kumpulan data tersebut. Kami menggunakan strategi {merge:true}
yang mengganti 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 update sebelumnya atau pembuatan awal).
Kita juga menetapkan header Location
agar mengarah ke URI buku.
DELETE /books/:isbn
Menghapus buku adalah hal yang 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}`});
}
});
Memulai server Express / Node
Terakhir, kita memulai server, 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, pertama-tama kita akan menginstal dependensi dengan:
$ npm install
Lalu, kita dapat mulai dengan:
$ npm start
Server akan dimulai pada localhost
dan memprosesnya 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
Berjalan di dalam Docker juga merupakan cara yang bagus untuk memeriksa kembali apakah containerization aplikasi kita berjalan dengan baik saat kita membangunnya di cloud dengan Cloud Build.
Menguji API
Terlepas dari cara kami menjalankan kode REST API (langsung melalui Node atau melalui image container Docker), kami kini dapat menjalankan beberapa kueri terhadapnya.
- Buat buku baru (ISBN dalam 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
- Buat buku baru (ISBN di 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
- Menghapus buku (yang kita buat):
$ curl -XDELETE http://localhost:8080/books/9782070368228
- Mengambil buku berdasarkan ISBN:
$ curl http://localhost:8080/books/9780140449136 $ curl http://localhost:8080/books/9782070360536
- Perbarui buku yang ada dengan mengubah judulnya saja:
$ 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
- Mencari 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 dalam container
Kami senang bahwa REST API ini berfungsi sesuai rencana, jadi sekarang adalah 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, menggunakan perintah berikut:
$ gcloud builds submit \ --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/crud-web-api
- Lalu, dengan men-deploy layanan dengan 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 kembali di UI Cloud Console bahwa layanan Cloud Run sekarang muncul dalam daftar:
Satu langkah terakhir yang akan kita lakukan di sini adalah mengambil URL layanan Cloud Run yang baru di-deploy, melalui perintah berikut:
$ export RUN_CRUD_SERVICE_URL=$(gcloud run services describe run-crud \ --region=${REGION} \ --platform=managed \ --format='value(status.url)')
Kita memerlukan URL Cloud Run REST API di bagian berikutnya karena kode frontend App Engine akan berinteraksi dengan API.
9. Menghosting aplikasi web untuk menjelajahi library
Bagian terakhir dari teka-teki untuk menambahkan glitter ke 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).
Aplikasi kita, meskipun di-deploy pada runtime Node.JS App Engine, sebagian besar terbuat dari resource statis. Kode backend tidak banyak, karena sebagian besar interaksi pengguna akan dilakukan di browser melalui JavaScript sisi klien. Kami tidak akan menggunakan framework JavaScript frontend yang canggih. Kami hanya akan menggunakan beberapa JavaScript "vanilla", dengan beberapa Komponen Web untuk UI yang menggunakan library komponen web Tali Sepatu:
- kotak pilihan untuk memilih bahasa buku:
- komponen kartu untuk menampilkan detail tentang buku tertentu (termasuk kode batang untuk mewakili ISBN buku, menggunakan koleksi JsBarcode):
- dan tombol untuk memuat lebih banyak buku dari database:
Ketika menggabungkan semua komponen visual tersebut, hasil halaman web untuk menjelajahi koleksi kami akan terlihat seperti berikut:
File konfigurasi app.yaml
Mari kita mulai mempelajari code base aplikasi App Engine ini, dengan melihat file konfigurasi app.yaml
-nya. File ini adalah file yang dikhususkan untuk App Engine, dan memungkinkan untuk mengonfigurasi hal-hal seperti variabel lingkungan, berbagai "pengendali" aplikasi, atau menentukan bahwa beberapa resource adalah aset statis, yang akan disalurkan 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 menetapkan bahwa aplikasi kita adalah Node.JS, dan kita ingin menggunakan versi 14.
Kemudian, kita tentukan variabel lingkungan yang menunjuk ke URL layanan Cloud Run. Kita harus memperbarui placeholder CHANGE_ME dengan URL yang benar (lihat cara mengubahnya di bawah).
Setelah itu, kita mendefinisikan berbagai pengendali. Tiga tombol 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. Dan yang terakhir adalah default yang akan mengarahkan semua URL lain (/.*
) ke aplikasi Node.JS kita (yaitu bagian "dinamis" dari aplikasi, berbeda dengan aset statis yang telah kita jelaskan).
Mari kita perbarui URL Web API untuk layanan Cloud Run sekarang.
Di direktori appengine-frontend/
, jalankan perintah berikut untuk memperbarui variabel lingkungan yang mengarah ke URL REST API berbasis Cloud Run kami:
$ sed -i -e "s|CHANGE_ME|${RUN_CRUD_SERVICE_URL}|" app.yaml
Atau ubah string CHANGE_ME
secara manual di app.yaml
dengan URL yang benar:
env_variables:
RUN_CRUD_SERVICE_URL: CHANGE_ME
File package.json
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"
}
}
Sekali lagi, kami tekankan bahwa kami ingin menjalankan aplikasi ini menggunakan Node.JS 14. Kami bergantung pada framework Express, serta modul NPM isbn3
untuk memvalidasi buku Kode ISBN.
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 secara otomatis / dimulai ulang saat perubahan:
$ npm run dev
Kode Node.JS index.js
const express = require('express');
const app = express();
app.use(express.static('public'));
const bodyParser = require('body-parser');
app.use(bodyParser.json());
Kami memerlukan framework web Express. Kami 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);
});
URL pertama yang cocok dengan /
akan dialihkan ke index.html
di direktori public/html
. Seperti dalam mode pengembangan, kita tidak berjalan dalam runtime App Engine, tetapi perutean URL App Engine tidak terjadi. Jadi, di sini kita hanya mengarahkan
URL {i>root<i} ke file HTML.
Endpoint kedua yang kita tentukan /webapi
akan menampilkan URL Cloud RUN REST API kita. Dengan begitu, kode JavaScript sisi klien akan tahu tempat untuk memanggil 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}`);
});
Sebagai penutup, kita menjalankan aplikasi web Express dan memproses di port 8080 secara default.
Halaman index.html
Kita tidak akan melihat setiap baris dari laman HTML yang panjang ini. Sebagai gantinya, mari kita soroti beberapa baris kunci.
<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 akan mengimpor library komponen web Tali Sepatu (skrip dan stylesheet).
Baris berikutnya mengimpor perpustakaan JsBarcode, untuk membuat kode batang dari kode ISBN buku.
Baris terakhir mengimpor kode JavaScript dan stylesheet CSS kita sendiri, yang terletak di subdirektori public/
.
Di body
halaman HTML, kami menggunakan komponen Tali Sepatu 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>
...
Dan kami juga menggunakan template HTML dan kemampuan pengisian slotnya untuk mewakili sebuah buku. Kita akan membuat salinan template tersebut untuk mengisi daftar buku, dan mengganti nilai di slot dengan detail buku:
<template id="book-card">
<sl-card class="card-overview">
...
<slot name="author">Author</slot>
...
</sl-card>
</template>
Cukup HTML, kami hampir selesai meninjau kode. Satu bagian penting terakhir yang tersisa: kode JavaScript sisi klien app.js
yang berinteraksi dengan REST API kita.
Kode JavaScript sisi klien app.js
Kita mulai dengan pemroses peristiwa tingkat atas yang menunggu konten DOM dimuat:
document.addEventListener("DOMContentLoaded", async function(event) {
...
}
Setelah siap, kita dapat menyiapkan beberapa konstanta dan variabel kunci:
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 awalnya ditetapkan di app.yaml
. Berkat variabel lingkungan, endpoint /webapi
, yang dipanggil dari kode sisi klien JavaScript, kami tidak perlu melakukan hardcode pada URL REST API di 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);
});
Kami menambahkan pengendali peristiwa pada tombol untuk memuat buku. Saat diklik, fungsi appendMoreBooks()
akan dipanggil.
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 sebuah {i>event handler<i} untuk diberi tahu tentang perubahan dalam pilihan bahasa. Seperti tombol tersebut, kita juga memanggil fungsi appendMoreBooks()
, dengan meneruskan URL REST API, halaman saat ini, dan pemilihan bahasa.
Jadi mari kita lihat fungsi tersebut 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. Ada tiga parameter kueri yang biasanya dapat kita tentukan, tetapi di sini, di UI ini, kita hanya menentukan dua:
page
— bilangan bulat yang menunjukkan halaman saat ini untuk penomoran halaman buku,language
— string bahasa untuk memfilter berdasarkan bahasa tertulis.
Kemudian, kami menggunakan Fetch API untuk mengambil array JSON yang berisi detail buku.
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 atau tidak, kita akan menampilkan atau menyembunyikan tombol [More books...]
, karena header Link
adalah petunjuk yang memberi tahu kita apakah ada lebih banyak buku yang masih harus 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 mewakili 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 sedikit lebih bagus, kami menggunakan koleksi JsBarcode untuk membuat kode batang yang bagus seperti di sampul belakang buku sungguhan.
Menjalankan dan menguji aplikasi secara lokal
Kode yang cukup untuk saat ini, saatnya untuk melihat cara kerja aplikasi. Pertama, kita akan melakukannya secara lokal, dalam Cloud Shell, sebelum melakukan deployment secara nyata.
Kami menginstal modul NPM yang dibutuhkan oleh aplikasi kami dengan:
$ npm install
Dan kita akan menjalankan aplikasi dengan seperti biasa:
$ npm start
Atau dengan pemuatan 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 berjalan dengan baik secara lokal, kini saatnya untuk men-deploy-nya di App Engine.
Untuk men-deploy aplikasi, mari jalankan perintah berikut:
$ gcloud app deploy -q
Setelah beberapa saat, aplikasi akan di-deploy.
Aplikasi akan tersedia di URL 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. Pembersihan (opsional)
Jika tidak ingin mempertahankan aplikasi, Anda dapat membersihkan resource untuk menghemat biaya dan menjadi cloud citizen yang baik secara keseluruhan dengan menghapus seluruh project:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
11. Selamat!
Berkat Cloud Functions, App Engine, dan Cloud Run, kami membuat serangkaian layanan untuk mengekspos berbagai endpoint Web API dan frontend web, guna menyimpan, memperbarui, dan menelusuri koleksi buku, dengan mengikuti beberapa pola desain yang baik untuk pengembangan REST API selama prosesnya.
Yang telah kita bahas
- Cloud Functions
- Cloud Firestore
- Cloud Run
- App Engine
Melangkah lebih jauh
Jika Anda ingin mengeksplorasi contoh konkret ini lebih lanjut dan memperluasnya, berikut adalah daftar hal-hal yang mungkin ingin Anda selidiki:
- Manfaatkan Gateway API untuk menyediakan fasad API umum ke fungsi impor data dan penampung REST API, untuk menambahkan fitur seperti menangani kunci API untuk mengakses API, atau menentukan batasan tarif bagi konsumen API.
- Deploy modul node Swagger-UI di aplikasi App Engine untuk mendokumentasikan dan menawarkan tempat pengujian untuk REST API tersebut.
- Di frontend, selain kemampuan penjelajahan yang ada, tambahkan layar tambahan untuk mengedit data, lalu buat entri buku baru. Selain itu, karena kita menggunakan database Cloud Firestore, manfaatkan fitur real-time untuk memperbarui data buku yang ditampilkan saat perubahan dibuat.