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 beserta thumbnail mereka.
Aplikasi web ini akan menggunakan framework CSS yang disebut Bulma, untuk memiliki antarmuka pengguna yang terlihat menarik, serta 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 menjelaskan gambar tersebut (yang terdeteksi oleh Cloud Vision API di lab sebelumnya).
- Halaman collage yang akan menampilkan kolase yang terbuat dari 4 foto 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 collage (
collage.html
) menunjuk gambarcollage.png
yang menyusun 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 kebijakan ini diaktifkan:
gcloud services enable compute.googleapis.com
Anda akan melihat operasi berhasil diselesaikan:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Meng-clone kode
Periksa kodenya, jika Anda belum melakukannya:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Selanjutnya, 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.js
berisi kode Node.jspackage.json
menentukan dependensi libraryapp.yaml
adalah file konfigurasi untuk Google App Engine
Folder public
berisi resource statis:
index.html
adalah halaman yang menampilkan semua gambar thumbnail dan labelcollage.html
menampilkan kolase foto terbaruupload.html
berisi formulir untuk mengupload foto baruapp.js
menggunakan Vue.js untuk mengisi halamanindex.html
dengan datascript.js
menangani menu navigasi dan "hamburger"-nya ikon di layar kecilstyle.css
menentukan beberapa perintah 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 kita bergantung pada:
- firestore: untuk mengakses Cloud Firestore dengan metadata gambar,
- 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 manusia,
- bluebird: library promise JavaScript,
- express-fileupload: library untuk menangani upload file dengan mudah.
Frontend Express
Di awal pengontrol index.js
, Anda akan mewajibkan 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 sub-direktoripublic
. - Selain itu,
fileUpload()
mengonfigurasi upload file hingga membatasi ukuran file hingga 10 MB, untuk mengupload file secara lokal dalam 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 aset 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/pictures
Melalui formulir di upload.html, foto akan diupload melalui permintaan POSTGET /api/pictures
Endpoint ini menampilkan dokumen JSON yang berisi daftar gambar dan labelnyaGET /api/pictures/:name
URL ini mengalihkan ke lokasi penyimpanan cloud dari gambar ukuran penuhGET /api/thumbnails/:name
URL ini mengalihkan ke lokasi penyimpanan cloud dari gambar thumbnailGET /api/collage
URL terakhir ini mengalihkan pengguna ke lokasi penyimpanan cloud dari gambar kolase yang dihasilkan
Upload foto
Sebelum mempelajari gambar yang diupload kode Node.js, 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 POST HTTP, 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, periksa apakah memang ada file yang diupload. Kemudian, download file secara lokal melalui metode mv
yang berasal dari modul Node upload file kami. Setelah file tersedia di sistem file lokal, Anda dapat mengupload gambar ke bucket Cloud Storage. Terakhir, Anda mengalihkan pengguna kembali ke layar utama aplikasi.
Membuat daftar gambar
Saatnya untuk menampilkan foto-foto yang indah!
Di pengendali /api/pictures
, Anda melihat koleksi pictures
database Firestore untuk mengambil semua gambar (yang thumbnail-nya telah dibuat), yang diurutkan berdasarkan tanggal pembuatan yang menurun.
Anda mengirim setiap gambar dalam array JavaScript, dengan namanya, label yang mendeskripsikannya (berasal dari Cloud Vision API), warna dominan, dan tanggal pembuatan yang mudah (dengan dayjs
, kita memiliki beda waktu relatif seperti "3 days from now").
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 dengan 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 markup yang disederhanakan 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 Vue.js bahwa ini adalah bagian dari markup yang akan dirender secara dinamis. Iterasi selesai berkat perintah v-for
.
Gambar mendapatkan batas berwarna yang bagus sesuai dengan warna dominan pada gambar, seperti yang ditemukan oleh Cloud Vision API dan kami mengarahkannya ke thumbnail dan gambar lebar penuh di link dan sumber gambar.
Terakhir, kita buat daftar label yang menjelaskan gambar.
Berikut adalah kode JavaScript untuk cuplikan Vue.js (di 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
. Data yang ditampilkan akan diikat ke kode tampilan di markup yang Anda lihat sebelumnya.
Melihat gambar
Dari index.html
, pengguna kita dapat melihat thumbnail gambar, mengkliknya untuk melihat gambar ukuran penuh, dan dari collage.html
, pengguna melihat gambar collage.png
.
Dalam markup HTML halaman tersebut, gambar src
dan link href
mengarah pada 3 endpoint tersebut, yang mengalihkan ke lokasi Cloud Storage gambar, thumbnail, dan kolase. Tidak perlu melakukan hard code jalur di 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 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 di-deploy ke cloud.
Anda harus 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 dengan baik, 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 asli 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.
Mengonfigurasi 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 mendeklarasikan bahwa runtime didasarkan pada Node.js 10. Dua variabel lingkungan ditetapkan untuk menunjuk ke kedua bucket, untuk gambar asli dan 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
Dan deploy:
gcloud app deploy
Setelah satu atau dua menit, Anda akan diberi tahu bahwa aplikasi menyalurkan 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 mengunjungi bagian App Engine di Konsol Cloud untuk melihat apakah aplikasi sudah di-deploy dan mempelajari fitur App Engine seperti pembuatan versi dan pemisahan traffic:
8. Menguji aplikasi
Untuk menguji, buka URL App Engine default untuk aplikasi aplikasi (https://<YOUR_PROJECT_ID>.appspot.com/
) dan Anda akan melihat UI frontend aktif dan berjalan.
9. 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}
10. Selamat!
Selamat! Aplikasi web Node.js yang dihosting di App Engine ini mengikat semua layanan Anda, serta memungkinkan pengguna mengupload dan memvisualisasikan gambar.
Yang telah kita bahas
- App Engine
- Cloud Storage
- Cloud Firestore