1. Ringkasan
Dalam codelab ini, Anda akan membuat frontend web di Google App Engine, yang memungkinkan pengguna mengupload gambar dari aplikasi web, serta menjelajahi gambar yang diupload dan thumbnailnya.

Aplikasi web ini akan menggunakan framework CSS bernama Bulma, untuk memiliki antarmuka pengguna yang terlihat bagus, dan juga framework frontend JavaScript Vue.JS untuk memanggil API aplikasi yang akan Anda bangun.
Aplikasi ini akan terdiri dari tiga tab:
- Halaman beranda yang akan menampilkan thumbnail semua gambar yang diupload, beserta daftar label yang mendeskripsikan gambar (yang terdeteksi oleh Cloud Vision API di lab sebelumnya).
- Halaman kolase yang akan menampilkan kolase yang dibuat dari 4 gambar terbaru yang diupload.
- Halaman upload, tempat pengguna dapat mengupload gambar baru.
Frontend yang dihasilkan akan terlihat seperti berikut:

Ketiga halaman tersebut adalah halaman HTML sederhana:
- Halaman beranda (
index.html) memanggil kode backend Node App Engine untuk mendapatkan daftar gambar thumbnail dan labelnya, melalui panggilan AJAX ke URL/api/pictures. Halaman beranda menggunakan Vue.js untuk mengambil data ini. - Halaman kolase (
collage.html) mengarah ke gambarcollage.pngyang menggabungkan 4 gambar terbaru. - Halaman upload (
upload.html) menawarkan formulir sederhana untuk mengupload gambar melalui permintaan POST ke URL/api/pictures.
Yang akan Anda pelajari
- App Engine
- Cloud Storage
- Cloud Firestore
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.



- Nama project adalah nama tampilan untuk peserta project ini. String ini adalah string karakter yang tidak digunakan oleh Google API, dan Anda dapat memperbaruinya kapan saja.
- Project ID harus unik di semua project Google Cloud dan tidak dapat diubah (tidak dapat diubah setelah ditetapkan). Cloud Console otomatis menghasilkan string unik; biasanya Anda tidak peduli dengan kata-katanya. Pada sebagian besar codelab, Anda harus mereferensikan Project ID (dan biasanya diidentifikasi sebagai
PROJECT_ID). Jadi, jika Anda tidak menyukainya, buat ID acak lain, atau, Anda dapat mencoba sendiri dan melihat apakah tersedia. Kemudian file akan "dibekukan" setelah project dibuat. - Ada nilai ketiga, Nomor Project yang digunakan oleh beberapa API. Pelajari lebih lanjut ketiga nilai ini di dokumentasi.
- Selanjutnya, Anda harus mengaktifkan penagihan di Cloud Console untuk menggunakan API/resource Cloud. Menjalankan operasi dalam codelab ini seharusnya tidak memerlukan banyak biaya, bahkan mungkin tidak sama sekali. Untuk menonaktifkan resource agar tidak menimbulkan penagihan di luar tutorial ini, ikuti petunjuk "pembersihan" yang ada di akhir codelab. 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 di lab ini dapat dilakukan hanya dengan browser.
3. Mengaktifkan API
App Engine memerlukan Compute Engine API. Pastikan sudah diaktifkan:
gcloud services enable compute.googleapis.com
Anda akan melihat operasi selesai dengan berhasil:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Buat clone kode
Periksa kode, jika Anda belum melakukannya:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Kemudian, Anda dapat membuka direktori yang berisi frontend:
cd serverless-photosharing-workshop/frontend
Anda akan memiliki tata letak file berikut untuk frontend:
frontend
|
├── index.js
├── package.json
├── app.yaml
|
├── public
|
├── index.html
├── collage.html
├── upload.html
|
├── app.js
├── script.js
├── style.css
Di root project, Anda memiliki 3 file:
index.jsberisi kode Node.jspackage.jsonmenentukan dependensi libraryapp.yamladalah file konfigurasi untuk Google App Engine
Folder public berisi resource statis:
index.htmladalah halaman yang menampilkan semua gambar thumbnail dan labelcollage.htmlmenampilkan kolase foto terbaruupload.htmlberisi formulir untuk mengupload gambar baruapp.jsmenggunakan Vue.js untuk mengisi halamanindex.htmldengan datascript.jsmenangani menu navigasi dan ikon "tiga garis" di layar kecilstyle.cssmenentukan beberapa direktif CSS
5. Mempelajari kode
Dependensi
File package.json menentukan dependensi library yang diperlukan:
{
"name": "frontend",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@google-cloud/firestore": "^3.4.1",
"@google-cloud/storage": "^4.0.0",
"express": "^4.16.4",
"dayjs": "^1.8.22",
"bluebird": "^3.5.0",
"express-fileupload": "^1.1.6"
}
}
Aplikasi kami bergantung pada:
- firestore: untuk mengakses Cloud Firestore dengan metadata gambar kami,
- storage: untuk mengakses Google Cloud Storage tempat gambar disimpan,
- express: framework web untuk Node.js,
- dayjs: library kecil untuk menampilkan tanggal dengan cara yang mudah dipahami,
- bluebird: library promise JavaScript,
- express-fileupload: library untuk menangani upload file dengan mudah.
Frontend Express
Di awal pengontrol index.js, Anda akan memerlukan semua dependensi yang ditentukan di package.json sebelumnya:
const express = require('express');
const fileUpload = require('express-fileupload');
const Firestore = require('@google-cloud/firestore');
const Promise = require("bluebird");
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const path = require('path');
const dayjs = require('dayjs');
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)
Selanjutnya, instance aplikasi Express dibuat.
Dua middleware Express digunakan:
- Panggilan
express.static()menunjukkan bahwa resource statis akan tersedia di subdirektoripublic. - Selain itu,
fileUpload()mengonfigurasi upload file untuk membatasi ukuran file hingga 10 MB, guna mengupload file secara lokal di sistem file dalam memori di direktori/tmp.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
limits: { fileSize: 10 * 1024 * 1024 },
useTempFiles : true,
tempFileDir : '/tmp/'
}))
Di antara resource statis, Anda memiliki file HTML untuk halaman beranda, halaman kolase, dan halaman upload. Halaman tersebut akan memanggil backend API. API ini akan memiliki endpoint berikut:
POST /api/picturesMelalui formulir di upload.html, gambar akan diupload melalui permintaan POSTGET /api/picturesEndpoint ini menampilkan dokumen JSON yang berisi daftar gambar dan labelnyaGET /api/pictures/:nameURL ini mengalihkan ke lokasi penyimpanan cloud dari gambar ukuran penuhGET /api/thumbnails/:nameURL ini mengalihkan ke lokasi penyimpanan cloud gambar thumbnailGET /api/collageURL terakhir ini mengalihkan ke lokasi penyimpanan cloud dari gambar kolase yang dihasilkan
Upload foto
Sebelum mempelajari kode Node.js untuk upload gambar, lihat sekilas public/upload.html.
...
<form method="POST" action="/api/pictures" enctype="multipart/form-data">
...
<input type="file" name="pictures">
<button>Submit</button>
...
</form>
...
Elemen formulir mengarah ke endpoint /api/pictures, dengan metode HTTP POST, dan format multi-bagian. index.js sekarang harus merespons endpoint dan metode tersebut, serta mengekstrak file:
app.post('/api/pictures', async (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
console.log("No file uploaded");
return res.status(400).send('No file was uploaded.');
}
console.log(`Receiving files ${JSON.stringify(req.files.pictures)}`);
const pics = Array.isArray(req.files.pictures) ? req.files.pictures : [req.files.pictures];
pics.forEach(async (pic) => {
console.log('Storing file', pic.name);
const newPicture = path.resolve('/tmp', pic.name);
await pic.mv(newPicture);
const pictureBucket = storage.bucket(process.env.BUCKET_PICTURES);
await pictureBucket.upload(newPicture, { resumable: false });
});
res.redirect('/');
});
Pertama, Anda memeriksa bahwa memang ada file yang sedang diupload. Kemudian, Anda mendownload file secara lokal melalui metode mv yang berasal dari modul Node upload file kami. Setelah file tersedia di sistem file lokal, Anda mengupload gambar ke bucket Cloud Storage. Terakhir, Anda mengalihkan pengguna kembali ke layar utama aplikasi.
Mencantumkan gambar
Saatnya menampilkan foto-foto indah Anda.
Di handler /api/pictures, Anda melihat koleksi pictures database Firestore, untuk mengambil semua gambar (yang thumbnail-nya telah dibuat), yang diurutkan berdasarkan tanggal pembuatan secara menurun.
Anda memasukkan setiap gambar dalam array JavaScript, dengan nama, label yang mendeskripsikannya (berasal dari Cloud Vision API), warna dominan, dan tanggal pembuatan yang mudah dipahami (dengan dayjs, kita dapat menggunakan offset waktu relatif seperti "3 hari dari sekarang").
app.get('/api/pictures', async (req, res) => {
console.log('Retrieving list of pictures');
const thumbnails = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
.where('thumbnail', '==', true)
.orderBy('created', 'desc').get();
if (snapshot.empty) {
console.log('No pictures found');
} else {
snapshot.forEach(doc => {
const pic = doc.data();
thumbnails.push({
name: doc.id,
labels: pic.labels,
color: pic.color,
created: dayjs(pic.created.toDate()).fromNow()
});
});
}
console.table(thumbnails);
res.send(thumbnails);
});
Pengontrol ini menampilkan hasil dalam bentuk berikut:
[
{
"name": "IMG_20180423_163745.jpg",
"labels": [
"Dish",
"Food",
"Cuisine",
"Ingredient",
"Orange chicken",
"Produce",
"Meat",
"Staple food"
],
"color": "#e78012",
"created": "a day ago"
},
...
]
Struktur data ini digunakan oleh cuplikan Vue.js kecil dari halaman index.html. Berikut adalah versi sederhana markup dari halaman tersebut:
<div id="app">
<div class="container" id="app">
<div id="picture-grid">
<div class="card" v-for="pic in pictures">
<div class="card-content">
<div class="content">
<div class="image-border" :style="{ 'border-color': pic.color }">
<a :href="'/api/pictures/' + pic.name">
<img :src="'/api/thumbnails/' + pic.name">
</a>
</div>
<a class="panel-block" v-for="label in pic.labels" :href="'/?q=' + label">
<span class="panel-icon">
<i class="fas fa-bookmark"></i>
</span>
{{ label }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
ID div akan menunjukkan kepada Vue.js bahwa ID tersebut adalah bagian dari markup yang akan dirender secara dinamis. Iterasi dilakukan berkat arahan v-for.
Gambar mendapatkan batas berwarna yang bagus sesuai dengan warna dominan dalam gambar, seperti yang ditemukan oleh Cloud Vision API, dan kami mengarahkan ke thumbnail dan gambar lebar penuh dalam link dan sumber gambar.
Terakhir, kita mencantumkan label yang menjelaskan gambar.
Berikut adalah kode JavaScript untuk cuplikan Vue.js (dalam file public/app.js yang diimpor di bagian bawah halaman index.html):
var app = new Vue({
el: '#app',
data() {
return { pictures: [] }
},
mounted() {
axios
.get('/api/pictures')
.then(response => { this.pictures = response.data })
}
})
Kode Vue menggunakan library Axios untuk melakukan panggilan AJAX ke endpoint /api/pictures kami. Data yang ditampilkan kemudian diikat ke kode tampilan dalam markup yang Anda lihat sebelumnya.
Melihat gambar
Dari index.html, pengguna kami dapat melihat thumbnail gambar, mengkliknya untuk melihat gambar berukuran penuh, dan dari collage.html, pengguna melihat gambar collage.png.
Dalam markup HTML halaman tersebut, gambar src dan link href mengarah ke 3 endpoint tersebut, yang mengalihkan ke lokasi Cloud Storage gambar, thumbnail, dan kolase. Tidak perlu melakukan hard code jalur dalam markup HTML.
app.get('/api/pictures/:name', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_PICTURES}/${req.params.name}`);
});
app.get('/api/thumbnails/:name', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/${req.params.name}`);
});
app.get('/api/collage', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/collage.png`);
});
Menjalankan aplikasi Node
Setelah semua endpoint ditentukan, aplikasi Node.js Anda siap diluncurkan. Aplikasi Express memproses di port 8080 secara default, dan siap melayani permintaan masuk.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started web frontend service on port ${PORT}`);
console.log(`- Pictures bucket = ${process.env.BUCKET_PICTURES}`);
console.log(`- Thumbnails bucket = ${process.env.BUCKET_THUMBNAILS}`);
});
6. Menguji secara lokal
Uji kode secara lokal untuk memastikan kode berfungsi sebelum men-deploy ke cloud.
Anda perlu mengekspor dua variabel lingkungan yang sesuai dengan dua bucket Cloud Storage:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT}
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Di dalam folder frontend, instal dependensi npm dan mulai server:
npm install; npm start
Jika semuanya berjalan lancar, server akan dimulai pada port 8080:
Started web frontend service on port 8080
- Pictures bucket = uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
- Thumbnails bucket = thumbnails-${GOOGLE_CLOUD_PROJECT}
Nama sebenarnya bucket Anda akan muncul di log tersebut, yang berguna untuk tujuan proses debug.
Dari Cloud Shell, Anda dapat menggunakan fitur pratinjau web untuk menjelajahi aplikasi yang berjalan secara lokal:

Gunakan CTRL-C untuk keluar.
7. Men-deploy ke App Engine
Aplikasi Anda siap di-deploy.
Konfigurasi App Engine
Periksa file konfigurasi app.yaml untuk App Engine:
runtime: nodejs16 env_variables: BUCKET_PICTURES: uploaded-pictures-GOOGLE_CLOUD_PROJECT BUCKET_THUMBNAILS: thumbnails-GOOGLE_CLOUD_PROJECT
Baris pertama menyatakan bahwa runtime didasarkan pada Node.js 10. Dua variabel lingkungan ditentukan untuk mengarah ke dua bucket, untuk gambar asli dan untuk thumbnail.
Untuk mengganti GOOGLE_CLOUD_PROJECT dengan project ID Anda yang sebenarnya, Anda dapat menjalankan perintah berikut:
sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml
Deploy
Tetapkan region pilihan Anda untuk App Engine, pastikan untuk menggunakan region yang sama di lab sebelumnya:
gcloud config set compute/region europe-west1
Lalu deploy:
gcloud app deploy
Setelah satu atau dua menit, Anda akan diberi tahu bahwa aplikasi menyajikan traffic:
Beginning deployment of service [default]... ╔════════════════════════════════════════════════════════════╗ ╠═ Uploading 8 files to Google Cloud Storage ═╣ ╚════════════════════════════════════════════════════════════╝ File upload done. Updating service [default]...done. Setting traffic split for service [default]...done. Deployed service [default] to [https://GOOGLE_CLOUD_PROJECT.appspot.com] You can stream logs from the command line by running: $ gcloud app logs tail -s default To view your application in the web browser run: $ gcloud app browse
Anda juga dapat membuka bagian App Engine di Cloud Console untuk melihat bahwa aplikasi telah di-deploy dan menjelajahi fitur App Engine seperti pembuatan versi dan pemisahan traffic:

8. Menguji aplikasi
Untuk menguji, buka URL App Engine default untuk aplikasi (https://<YOUR_PROJECT_ID>.appspot.com/) dan Anda akan melihat UI frontend berjalan dan beroperasi.

9. 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}
10. Selamat!
Selamat! Aplikasi web Node.js yang dihosting di App Engine ini mengikat semua layanan Anda, dan memungkinkan pengguna Anda mengupload dan memvisualisasikan gambar.
Yang telah kita bahas
- App Engine
- Cloud Storage
- Cloud Firestore