Bagian Belakang: Menggunakan Gatsby.js dan Node.js untuk Pembaruan Situs Statis
Diterbitkan: 2022-03-11Dalam seri artikel ini, kami akan mengembangkan prototipe situs web konten statis. Ini akan menghasilkan halaman HTML statis sederhana yang diperbarui setiap hari untuk repositori GitHub populer untuk melacak rilis terbaru mereka. Kerangka kerja pembuatan halaman web statis memiliki fitur hebat untuk mencapai itu—kami akan menggunakan Gatsby.js, salah satu yang paling populer.
Di Gatsby, ada banyak cara untuk mengumpulkan data untuk front end tanpa harus back end (serverless), platform Headless CMS dan plugin sumber Gatsby di antaranya. Namun kami akan menerapkan back end untuk menyimpan informasi dasar tentang repositori GitHub dan rilis terbarunya. Dengan demikian, kita akan memiliki kendali penuh atas ujung belakang dan ujung depan kita.
Juga, saya akan membahas seperangkat alat untuk memicu pembaruan harian aplikasi Anda. Anda juga dapat memicunya secara manual atau kapan pun beberapa peristiwa tertentu terjadi.
Aplikasi front-end kami akan dijalankan di Netlify, dan aplikasi back-end akan bekerja di Heroku menggunakan paket gratis. Ini akan tidur secara berkala: "Ketika seseorang mengakses aplikasi, manajer dyno akan secara otomatis membangunkan dyno web untuk menjalankan jenis proses web." Jadi, kita bisa membangunkannya melalui AWS Lambda dan AWS CloudWatch. Pada tulisan ini, ini adalah cara yang paling hemat biaya untuk memiliki prototipe online 24/7.
Contoh Situs Web Statis Node Kami: Apa yang Diharapkan
Agar artikel ini tetap fokus pada satu topik, saya tidak akan membahas otentikasi, validasi, skalabilitas, atau topik umum lainnya. Bagian pengkodean artikel ini akan sesederhana mungkin. Struktur proyek dan penggunaan seperangkat alat yang benar lebih penting.
Di bagian pertama dari seri ini, kami akan mengembangkan dan menerapkan aplikasi back-end kami. Di bagian kedua, kami akan mengembangkan dan men-deploy aplikasi front-end kami, dan memicu build harian.
Bagian Belakang Node.js
Aplikasi back-end akan ditulis dalam Node.js (tidak wajib, tetapi untuk kesederhanaan) dan semua komunikasi akan melalui REST API. Kami tidak akan mengumpulkan data dari front end dalam proyek ini. (Jika Anda tertarik untuk melakukannya, lihat Formulir Gatsby.)
Pertama, kita akan mulai dengan mengimplementasikan REST API back end sederhana yang mengekspos operasi CRUD dari koleksi repositori di MongoDB kita. Kemudian kami akan menjadwalkan pekerjaan cron yang menggunakan GitHub API v4 (GraphQL) untuk memperbarui dokumen dalam koleksi ini. Kemudian kami akan menyebarkan semua ini ke cloud Heroku. Akhirnya, kami akan memicu pembangunan kembali ujung depan di akhir tugas cron kami.
Bagian Depan Gatsby.js
Pada artikel kedua, kita akan fokus pada implementasi createPages
API. Kami akan mengumpulkan semua repositori dari bagian belakang dan akan menghasilkan satu halaman beranda yang berisi daftar semua repositori, ditambah halaman untuk setiap dokumen repositori yang dikembalikan. Kemudian kita akan menyebarkan front end kita ke Netlify.
Dari AWS Lambda dan AWS CloudWatch
Bagian ini tidak wajib jika aplikasi Anda tidak bisa tidur. Jika tidak, Anda harus memastikan bahwa back end Anda aktif dan berjalan pada saat memperbarui repositori. Sebagai solusi, Anda dapat membuat jadwal cron di AWS CloudWatch 10 menit sebelum pembaruan harian dan mengikatnya sebagai pemicu untuk metode GET
Anda di AWS Lambda. Mengakses aplikasi back-end akan membangunkan instance Heroku. Selengkapnya akan ada di akhir artikel kedua.
Berikut adalah arsitektur yang akan kami implementasikan:
Asumsi
Saya berasumsi bahwa pembaca artikel ini memiliki pengetahuan di bidang-bidang berikut:
- HTML
- CSS
- JavaScript
- API REST
- MongoDB
- Git
- Node.js
Ada baiknya juga jika Anda tahu:
- Express.js
- Luwak
- GitHub API v4 (GraphQL)
- Heroku, AWS, atau platform cloud lainnya
- Reaksi
Mari selami implementasi back end. Kami akan membaginya menjadi dua tugas. Yang pertama adalah menyiapkan titik akhir REST API dan mengikatnya ke koleksi repositori kami. Yang kedua adalah mengimplementasikan tugas cron yang menggunakan GitHub API dan memperbarui koleksi.
Mengembangkan Back End Generator Situs Statis Node.js, Langkah 1: API REST Sederhana
Kami akan menggunakan Express untuk kerangka aplikasi web kami dan Mongoose untuk koneksi MongoDB kami. Jika Anda sudah familiar dengan Express dan Mongoose, Anda mungkin bisa melompat ke Langkah 2.
(Di sisi lain, jika Anda membutuhkan lebih banyak keakraban dengan Express, Anda dapat melihat panduan starter Express resmi; jika Anda tidak memahami Mongoose, panduan starter resmi Mongoose akan membantu.)
Struktur Proyek
Hirarki file/folder proyek kami akan sederhana:
Lebih detail:
-
env.config.js
adalah file konfigurasi variabel lingkungan -
routes.config.js
adalah untuk memetakan titik akhir istirahat -
repository.controller.js
berisi metode untuk bekerja pada model repositori kami -
repository.model.js
berisi skema MongoDB dari repositori dan operasi CRUD -
index.js
adalah kelas penginisialisasi -
package.json
berisi dependensi dan properti proyek
Penerapan
Jalankan npm install
(atau yarn
, jika Anda telah menginstal Yarn) setelah menambahkan dependensi ini ke package.json
:
{ // ... "dependencies": { "body-parser": "1.7.0", "express": "^4.8.7", "moment": "^2.17.1", "moment-timezone": "^0.5.13", "mongoose": "^5.1.1", "node-uuid": "^1.4.8", "sync-request": "^4.0.2" } // ... }
File env.config.js
kami hanya memiliki port
, environment
( dev
atau prod
), dan properti mongoDbUri
untuk saat ini:
module.exports = { "port": process.env.PORT || 3000, "environment": "dev", "mongoDbUri": process.env.MONGODB_URI || "mongodb://localhost/github-consumer" };
routes.config.js
berisi pemetaan permintaan dan akan memanggil metode yang sesuai dari pengontrol kami:
const RepositoryController = require('../controller/repository.controller'); exports.routesConfig = function(app) { app.post('/repositories', [ RepositoryController.insert ]); app.get('/repositories', [ RepositoryController.list ]); app.get('/repositories/:id', [ RepositoryController.findById ]); app.patch('/repositories/:id', [ RepositoryController.patchById ]); app.delete('/repositories/:id', [ RepositoryController.deleteById ]); };
File repository.controller.js
adalah lapisan layanan kami. Tanggung jawabnya adalah memanggil metode yang sesuai dari model repositori kami:
const RepositoryModel = require('../model/repository.model'); exports.insert = (req, res) => { RepositoryModel.create(req.body) .then((result) => { res.status(201).send({ id: result._id }); }); }; exports.findById = (req, res) => { RepositoryModel.findById(req.params.id) .then((result) => { res.status(200).send(result); }); }; exports.list = (req, res) => { RepositoryModel.list() .then((result) => { res.status(200).send(result); }) }; exports.patchById = (req, res) => { RepositoryModel.patchById(req.params.id, req.body) .then(() => { res.status(204).send({}); }); }; exports.deleteById = (req, res) => { RepositoryModel.deleteById(req.params.id, req.body) .then(() => { res.status(204).send({}); }); };
repository.model.js
menangani koneksi MongoDb dan operasi CRUD untuk model repositori. Bidang model adalah:
-
owner
: Pemilik repositori (perusahaan atau pengguna) -
name
: Nama repositori -
createdAt
: Tanggal pembuatan rilis terakhir -
resourcePath
: Jalur rilis terakhir -
tagName
: Tag rilis terakhir -
releaseDescription
: Catatan rilis -
homepageUrl
: URL beranda proyek -
repositoryDescription
: Deskripsi repositori -
avatarUrl
: URL avatar pemilik proyek
const Mongoose = require('mongoose'); const Config = require('../config/env.config'); const MONGODB_URI = Config.mongoDbUri; Mongoose.connect(MONGODB_URI, { useNewUrlParser: true }); const Schema = Mongoose.Schema; const repositorySchema = new Schema({ owner: String, name: String, createdAt: String, resourcePath: String, tagName: String, releaseDescription: String, homepageUrl: String, repositoryDescription: String, avatarUrl: String }); repositorySchema.virtual('id').get(function() { return this._id.toHexString(); }); // Ensure virtual fields are serialised. repositorySchema.set('toJSON', { virtuals: true }); repositorySchema.findById = function(cb) { return this.model('Repository').find({ id: this.id }, cb); }; const Repository = Mongoose.model('repository', repositorySchema); exports.findById = (id) => { return Repository.findById(id) .then((result) => { if (result) { result = result.toJSON(); delete result._id; delete result.__v; return result; } }); }; exports.create = (repositoryData) => { const repository = new Repository(repositoryData); return repository.save(); }; exports.list = () => { return new Promise((resolve, reject) => { Repository.find() .exec(function(err, users) { if (err) { reject(err); } else { resolve(users); } }) }); }; exports.patchById = (id, repositoryData) => { return new Promise((resolve, reject) => { Repository.findById(id, function(err, repository) { if (err) reject(err); for (let i in repositoryData) { repository[i] = repositoryData[i]; } repository.save(function(err, updatedRepository) { if (err) return reject(err); resolve(updatedRepository); }); }); }) }; exports.deleteById = (id) => { return new Promise((resolve, reject) => { Repository.deleteOne({ _id: id }, (err) => { if (err) { reject(err); } else { resolve(err); } }); }); }; exports.findByOwnerAndName = (owner, name) => { return Repository.find({ owner: owner, name: name }); };
Inilah yang kami miliki setelah komit pertama kami: Koneksi MongoDB dan operasi REST kami.

Kita dapat menjalankan aplikasi kita dengan perintah berikut:
node index.js
Pengujian
Untuk pengujian, kirim permintaan ke localhost:3000
(menggunakan misalnya Postman atau cURL):
Sisipkan Repositori (Hanya Kolom yang Diperlukan)
Posting: http://localhost:3000/repositories
Tubuh:
{ "owner" : "facebook", "name" : "react" }
Dapatkan Repositori
Dapatkan: http://localhost:3000/repositories
Dapatkan dengan ID
Dapatkan: http://localhost:3000/repositories/:id
Tambalan berdasarkan ID
Patch: http://localhost:3000/repositories/:id
Tubuh:
{ "owner" : "facebook", "name" : "facebook-android-sdk" }
Dengan itu berfungsi, saatnya untuk mengotomatiskan pembaruan.
Mengembangkan Back End Generator Situs Statis Node.js, Langkah 2: Pekerjaan Cron untuk Memperbarui Rilis Repositori
Di bagian ini, kami akan mengonfigurasi tugas cron sederhana (yang akan dimulai pada tengah malam UTC) untuk memperbarui repositori GitHub yang kami masukkan ke database kami. Kami hanya menambahkan parameter owner
dan name
hanya dalam contoh kami di atas, tetapi dua bidang ini cukup bagi kami untuk mengakses informasi umum tentang repositori yang diberikan.
Untuk memperbarui data kami, kami harus menggunakan API GitHub. Untuk bagian ini, yang terbaik adalah mengenal GraphQL dan v4 dari GitHub API.
Kita juga perlu membuat token akses GitHub. Lingkup minimum yang diperlukan untuk itu adalah:
Itu akan menghasilkan token, dan kami dapat mengirim permintaan ke GitHub dengannya.
Sekarang mari kita kembali ke kode kita.
Kami memiliki dua dependensi baru di package.json
:
-
"axios": "^0.18.0"
adalah klien HTTP, jadi kami dapat membuat permintaan ke GitHub API -
"cron": "^1.7.0"
adalah penjadwal tugas cron
Seperti biasa, jalankan npm install
atau yarn
setelah menambahkan dependensi.
Kami juga membutuhkan dua properti baru di config.js
:
-
"githubEndpoint": "https://api.github.com/graphql"
-
"githubAccessToken": process.env.GITHUB_ACCESS_TOKEN
(Anda harus menyetel variabel lingkunganGITHUB_ACCESS_TOKEN
dengan token akses pribadi Anda sendiri)
Buat file baru di bawah folder controller
dengan nama cron.controller.js
. Ini hanya akan memanggil metode updateResositories
dari repository.controller.js
pada waktu yang dijadwalkan:
const RepositoryController = require('../controller/repository.controller'); const CronJob = require('cron').CronJob; function updateDaily() { RepositoryController.updateRepositories(); } exports.startCronJobs = function () { new CronJob('0 0 * * *', function () {updateDaily()}, null, true, 'UTC'); };
Perubahan terakhir untuk bagian ini akan berada di repository.controller.js
. Untuk singkatnya, kami akan mendesainnya untuk memperbarui semua repositori sekaligus. Tetapi jika Anda memiliki banyak repositori, Anda mungkin melebihi batasan sumber daya API GitHub. Jika itu masalahnya, Anda harus memodifikasi ini untuk berjalan dalam batch terbatas, tersebar dari waktu ke waktu.
Implementasi sekaligus dari fungsionalitas pembaruan akan terlihat seperti ini:
async function asyncUpdate() { await RepositoryModel.list().then((array) => { const promises = array.map(getLatestRelease); return Promise.all(promises); }); } exports.updateRepositories = async function update() { console.log('GitHub Repositories Update Started'); await asyncUpdate().then(() => { console.log('GitHub Repositories Update Finished'); }); };
Terakhir, kita akan memanggil titik akhir dan memperbarui model repositori.
Fungsi getLatestRelease
akan menghasilkan kueri GraphQL dan akan memanggil API GitHub. Respon dari permintaan tersebut kemudian akan diproses dalam fungsi updateDatabase
.
async function updateDatabase(responseData, owner, name) { let createdAt = ''; let resourcePath = ''; let tagName = ''; let releaseDescription = ''; let homepageUrl = ''; let repositoryDescription = ''; let avatarUrl = ''; if (responseData.repository.releases) { createdAt = responseData.repository.releases.nodes[0].createdAt; resourcePath = responseData.repository.releases.nodes[0].resourcePath; tagName = responseData.repository.releases.nodes[0].tagName; releaseDescription = responseData.repository.releases.nodes[0].description; homepageUrl = responseData.repository.homepageUrl; repositoryDescription = responseData.repository.description; if (responseData.organization && responseData.organization.avatarUrl) { avatarUrl = responseData.organization.avatarUrl; } else if (responseData.user && responseData.user.avatarUrl) { avatarUrl = responseData.user.avatarUrl; } const repositoryData = { owner: owner, name: name, createdAt: createdAt, resourcePath: resourcePath, tagName: tagName, releaseDescription: releaseDescription, homepageUrl: homepageUrl, repositoryDescription: repositoryDescription, avatarUrl: avatarUrl }; await RepositoryModel.findByOwnerAndName(owner, name) .then((oldGitHubRelease) => { if (!oldGitHubRelease[0]) { RepositoryModel.create(repositoryData); } else { RepositoryModel.patchById(oldGitHubRelease[0].id, repositoryData); } console.log(`Updated latest release: http://github.com${repositoryData.resourcePath}`); }); } } async function getLatestRelease(repository) { const owner = repository.owner; const name = repository.name; console.log(`Getting latest release for: http://github.com/${owner}/${name}`); const query = ` query { organization(login: "${owner}") { avatarUrl } user(login: "${owner}") { avatarUrl } repository(owner: "${owner}", name: "${name}") { homepageUrl description releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { createdAt resourcePath tagName description } } } }`; const jsonQuery = JSON.stringify({ query }); const headers = { 'User-Agent': 'Release Tracker', 'Authorization': `Bearer ${GITHUB_ACCESS_TOKEN}` }; await Axios.post(GITHUB_API_URL, jsonQuery, { headers: headers }).then((response) => { return updateDatabase(response.data.data, owner, name); }); }
Setelah komit kedua kami, kami akan menerapkan penjadwal cron untuk mendapatkan pembaruan harian dari repositori GitHub kami.
Kami hampir selesai dengan bagian belakang. Tapi langkah terakhir yang harus dilakukan setelah mengimplementasikan front end, jadi kita akan membahasnya di artikel berikutnya.
Menyebarkan Generator Situs Statis Node Back End ke Heroku
Pada langkah ini, kami akan menyebarkan aplikasi kami ke Heroku, jadi Anda harus menyiapkan akun dengan mereka jika Anda belum memilikinya. Jika kita mengikat akun Heroku kita ke GitHub, akan jauh lebih mudah bagi kita untuk melakukan penerapan berkelanjutan. Untuk itu, saya menghosting proyek saya di GitHub.
Setelah masuk ke akun Heroku Anda, tambahkan aplikasi baru dari dasbor:
Beri nama yang unik:
Anda akan diarahkan ke bagian penerapan. Pilih GitHub sebagai metode penerapan, cari repositori Anda, lalu klik tombol "Hubungkan":
Untuk mempermudah, Anda dapat mengaktifkan penerapan otomatis. Ini akan digunakan setiap kali Anda mendorong komit ke repo GitHub Anda:
Sekarang kita harus menambahkan MongoDB sebagai sumber daya. Buka tab Sumber Daya dan klik "Temukan lebih banyak pengaya." (Saya pribadi menggunakan mLab mongoDB.)
Instal dan masukkan nama aplikasi Anda di kotak input "Aplikasi untuk penyediaan":
Terakhir, kita harus membuat file bernama Procfile
di tingkat root proyek kita, yang menentukan perintah yang dijalankan oleh aplikasi saat Heroku memulainya.
Procfile
kami sesederhana ini:
web: node index.js
Buat file dan komit. Setelah Anda mendorong komit, Heroku akan secara otomatis menerapkan aplikasi Anda, yang akan dapat diakses sebagai https://[YOUR_UNIQUE_APP_NAME].herokuapp.com/
.
Untuk memeriksa apakah itu berfungsi, kami dapat mengirim permintaan yang sama dengan yang kami kirim ke localhost
.
Node.js, Express, MongoDB, Cron, dan Heroku: Kami Sudah Setengah Jalan!
Setelah komit ketiga kami, seperti inilah tampilan repo kami.
Sejauh ini, kami telah mengimplementasikan REST API berbasis Node.js/Express di bagian belakang kami, pembaru yang menggunakan API GitHub, dan tugas cron untuk mengaktifkannya. Kemudian kami telah menerapkan back end kami yang nantinya akan menyediakan data untuk generator konten web statis kami menggunakan Heroku dengan kait untuk integrasi berkelanjutan. Sekarang Anda siap untuk bagian kedua, di mana kami menerapkan ujung depan dan menyelesaikan aplikasi!