Arka Uç: Statik Site Güncellemeleri için Gatsby.js ve Node.js'yi Kullanma
Yayınlanan: 2022-03-11Bu yazı dizisinde statik içerikli bir web sitesi prototipi geliştireceğiz. Popüler GitHub depolarının en son sürümlerini izlemeleri için günlük olarak güncellenen, basit statik HTML sayfaları oluşturacaktır. Statik web sayfası oluşturma çerçeveleri bunu başarmak için harika özelliklere sahiptir - en popülerlerinden biri olan Gatsby.js'yi kullanacağız.
Gatsby'de, arka uç (sunucusuz), Headless CMS platformları ve Gatsby kaynak eklentileri olmadan bir ön uç için veri toplamanın birçok yolu vardır. Ancak GitHub depoları ve en son sürümleri hakkında temel bilgileri depolamak için bir arka uç uygulayacağız. Böylece hem arka ucumuz hem de ön ucumuz üzerinde tam kontrole sahip olacağız.
Ayrıca, uygulamanızın günlük güncellemesini tetikleyecek bir dizi araçtan bahsedeceğim. Ayrıca manuel olarak veya belirli bir olay gerçekleştiğinde tetikleyebilirsiniz.
Front-end uygulamamız Netlify üzerinde çalışacak ve back-end uygulaması ücretsiz bir plan kullanarak Heroku üzerinde çalışacak. Periyodik olarak uyur: "Birisi uygulamaya eriştiğinde, dyno yöneticisi web işlem türünü çalıştırmak için web dyno'yu otomatik olarak uyandırır." Böylece AWS Lambda ve AWS CloudWatch aracılığıyla uyandırabiliriz. Bu yazı itibariyle, 7/24 çevrimiçi bir prototipe sahip olmanın en uygun maliyetli yolu budur.
Düğüm Statik Web Sitesi Örneğimiz: Ne Beklemeli?
Bu makalelerin tek bir konuya odaklanmasını sağlamak için kimlik doğrulama, doğrulama, ölçeklenebilirlik veya diğer genel konuları ele almayacağım. Bu makalenin kodlama kısmı mümkün olduğunca basit olacaktır. Projenin yapısı ve doğru araç setinin kullanımı daha önemlidir.
Serinin bu ilk bölümünde, arka uç uygulamamızı geliştirip dağıtacağız. İkinci bölümde, ön uç uygulamamızı geliştirip dağıtacağız ve günlük derlemeleri tetikleyeceğiz.
Node.js Arka Ucu
Arka uç uygulaması Node.js'de yazılacak (zorunlu değil, basitlik için) ve tüm iletişimler REST API'leri üzerinden yapılacaktır. Bu projede ön uçtan veri toplamayacağız. (Bunu yapmakla ilgileniyorsanız, Gatsby Forms'a bir göz atın.)
İlk olarak, MongoDB'mizdeki depo koleksiyonunun CRUD işlemlerini ortaya çıkaran basit bir REST API arka ucunu uygulayarak başlayacağız. Ardından, bu koleksiyondaki belgeleri güncellemek için GitHub API v4 (GraphQL) tüketen bir cron işi planlayacağız. Sonra tüm bunları Heroku bulutuna dağıtacağız. Son olarak, cron işimizin sonunda ön ucun yeniden oluşturulmasını tetikleyeceğiz.
Gatsby.js Ön Ucu
İkinci makalede, createPages
API'sinin uygulanmasına odaklanacağız. Arka uçtaki tüm havuzları toplayacağız ve tüm havuzların bir listesini içeren tek bir ana sayfa ve ayrıca döndürülen her havuz belgesi için bir sayfa oluşturacağız. Ardından ön uçumuzu Netlify'a dağıtacağız.
AWS Lambda ve AWS CloudWatch'tan
Başvurunuz uykuya geçmiyorsa bu kısım zorunlu değildir. Aksi takdirde, depoları güncellerken arka ucunuzun çalışır durumda olduğundan emin olmanız gerekir. Çözüm olarak, günlük güncellemenizden 10 dakika önce AWS CloudWatch üzerinde bir cron zamanlaması oluşturabilir ve bunu AWS Lambda'daki GET
yönteminize tetikleyici olarak bağlayabilirsiniz. Arka uç uygulamasına erişim, Heroku örneğini uyandıracaktır. Daha fazla ayrıntı ikinci makalenin sonunda olacak.
İşte uygulayacağımız mimari:
varsayımlar
Bu makalenin okuyucularının aşağıdaki alanlarda bilgi sahibi olduğunu varsayıyorum:
- HTML
- CSS
- JavaScript
- REST API'leri
- MongoDB
- Git
- Node.js
Şunu da biliyorsan iyi olur:
- Express.js
- firavun faresi
- GitHub API v4 (GraphQL)
- Heroku, AWS veya başka herhangi bir bulut platformu
- Tepki
Arka ucun uygulanmasına geçelim. İki göreve ayıracağız. İlki, REST API uç noktaları hazırlamak ve bunları depo koleksiyonumuza bağlamaktır. İkincisi, GitHub API'sini tüketen ve koleksiyonu güncelleyen bir cron işi uygulamaktır.
Node.js Statik Site Oluşturucu Arka Uçunu Geliştirme, Adım 1: Basit Bir REST API
Web uygulama çerçevemiz için Express'i ve MongoDB bağlantımız için Mongoose'u kullanacağız. Express ve Mongoose hakkında bilginiz varsa 2. Adıma geçebilirsiniz.
(Öte yandan, Ekspres hakkında daha fazla bilgiye ihtiyacınız varsa, resmi Ekspres başlangıç kılavuzuna göz atabilirsiniz; Mongoose'a aşina değilseniz, resmi Mongoose başlangıç kılavuzu yardımcı olacaktır.)
Proje Yapısı
Projemizin dosya/klasör hiyerarşisi basit olacaktır:
Daha ayrıntılı olarak:
-
env.config.js
, ortam değişkenleri yapılandırma dosyasıdır -
routes.config.js
, kalan uç noktaları eşlemek içindir -
repository.controller.js
, depo modelimiz üzerinde çalışmak için yöntemler içerir -
repository.model.js
, depo ve CRUD işlemlerinin MongoDB şemasını içerir -
index.js
bir başlatıcı sınıfıdır -
package.json
, bağımlılıkları ve proje özelliklerini içerir
uygulama
Bu bağımlılıkları package.json
dosyasına ekledikten sonra npm install
(veya yarn
kuruluysa thread) çalıştırın:
{ // ... "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" } // ... }
env.config.js
dosyamız şimdilik sadece port
, environment
( dev
veya prod
) ve mongoDbUri
özelliklerine sahiptir:
module.exports = { "port": process.env.PORT || 3000, "environment": "dev", "mongoDbUri": process.env.MONGODB_URI || "mongodb://localhost/github-consumer" };
routes.config.js
, istek eşlemelerini içerir ve denetleyicimizin ilgili yöntemini çağırır:
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 ]); };
repository.controller.js
dosyası bizim hizmet katmanımızdır. Sorumluluğu, depo modelimizin ilgili yöntemini çağırmaktır:
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
, depo modeli için MongoDb bağlantısını ve CRUD işlemlerini yönetir. Modelin alanları şunlardır:
-
owner
: Depo sahibi (şirket veya kullanıcı) -
name
: Depo adı -
createdAt
: Son yayın oluşturma tarihi -
resourcePath
: Son yayın yolu -
tagName
: Son sürüm etiketi -
releaseDescription
: Sürüm notları -
homepageUrl
: Projenin ana URL'si -
repositoryDescription
: Depo açıklaması -
avatarUrl
: Proje sahibinin avatar URL'si
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 }); };
İlk taahhüdümüzden sonra sahip olduğumuz şey budur: Bir MongoDB bağlantısı ve REST operasyonlarımız.

Aşağıdaki komut ile uygulamamızı çalıştırabiliriz.
node index.js
Test yapmak
Test için istekleri localhost:3000
gönderin (örneğin Postacı veya cURL kullanarak):
Bir Depo Ekle (Yalnızca Gerekli Alanlar)
Gönderi: http://localhost:3000/repositories
Gövde:
{ "owner" : "facebook", "name" : "react" }
Depoları Alın
Alın: http://localhost:3000/repositories
Kimlikle al
Alın: http://localhost:3000/repositories/:id
Kimliğe göre yama
Yama: http://localhost:3000/repositories/:id
Gövde:
{ "owner" : "facebook", "name" : "facebook-android-sdk" }
Bu çalışma ile, güncellemeleri otomatikleştirmenin zamanı geldi.
Node.js Statik Site Oluşturucu Arka Uçunu Geliştirme, Adım 2: Depo Sürümlerini Güncellemek için Bir Cron İşi
Bu bölümde, veritabanımıza eklediğimiz GitHub depolarını güncellemek için basit bir cron işi (UTC gece yarısı başlayacak) yapılandıracağız. Yukarıdaki örneğimizde sadece owner
ve name
parametrelerini ekledik, ancak bu iki alan, verilen bir havuz hakkında genel bilgilere erişmemiz için yeterli.
Verilerimizi güncellemek için GitHub API'sini tüketmemiz gerekiyor. Bu bölüm için, GitHub API'sinin GraphQL ve v4'üne aşina olmak en iyisidir.
Ayrıca bir GitHub erişim belirteci oluşturmamız gerekiyor. Bunun için gerekli minimum kapsamlar şunlardır:
Bu bir belirteç oluşturacak ve onunla GitHub'a istek gönderebiliriz.
Şimdi kodumuza geri dönelim.
package.json
iki yeni bağımlılığımız var:
-
"axios": "^0.18.0"
bir HTTP istemcisidir, bu nedenle GitHub API'sine isteklerde bulunabiliriz -
"cron": "^1.7.0"
bir cron iş planlayıcısıdır
Her zamanki gibi, bağımlılıkları ekledikten sonra npm install
veya yarn
çalıştırın.
config.js
de iki yeni özelliğe ihtiyacımız olacak:
-
"githubEndpoint": "https://api.github.com/graphql"
-
"githubAccessToken": process.env.GITHUB_ACCESS_TOKEN
(GITHUB_ACCESS_TOKEN
ortam değişkenini kendi kişisel erişim jetonunuzla ayarlamanız gerekir)
controller
klasörü altında cron.controller.js
adıyla yeni bir dosya oluşturun. Planlanan zamanlarda repository.controller.js
updateResositories
yöntemini çağırması yeterlidir:
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'); };
Bu kısım için son değişiklikler repository.controller.js
içinde olacaktır. Kısaca, tüm depoları bir kerede güncellemek için tasarlayacağız. Ancak çok sayıda deponuz varsa GitHub'ın API'sinin kaynak sınırlamalarını aşabilirsiniz. Durum buysa, bunu zamana yayılmış sınırlı gruplar halinde çalışacak şekilde değiştirmeniz gerekecektir.
Güncelleme işlevinin hepsi bir arada uygulanması şöyle görünecektir:
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'); }); };
Son olarak, uç noktayı arayacağız ve depo modelini güncelleyeceğiz.
getLatestRelease
işlevi, bir GraphQL sorgusu oluşturacak ve GitHub API'sini çağıracaktır. Bu istekten gelen yanıt daha sonra updateDatabase
işlevinde işlenecektir.
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); }); }
İkinci taahhüdümüzden sonra GitHub depolarımızdan günlük güncellemeleri almak için bir cron zamanlayıcı uygulayacağız.
Arka uçla işimiz neredeyse bitti. Ancak son adım, ön ucu uyguladıktan sonra yapılmalıdır, bu yüzden bir sonraki makalede ele alacağız.
Düğüm Statik Site Oluşturucu Arka Ucunu Heroku'ya Dağıtma
Bu adımda, uygulamamızı Heroku'ya dağıtacağız, bu nedenle henüz bir hesabınız yoksa onlarla bir hesap oluşturmanız gerekecek. Heroku hesabımızı GitHub'a bağlarsak, sürekli dağıtım yapmamız çok daha kolay olacaktır. Bu amaçla projemi GitHub'da barındırıyorum.
Heroku hesabınıza giriş yaptıktan sonra kontrol panelinden yeni bir uygulama ekleyin:
Benzersiz bir ad verin:
Bir dağıtım bölümüne yönlendirileceksiniz. Dağıtım yöntemi olarak GitHub'ı seçin, deponuzu arayın ve ardından "Bağlan" düğmesini tıklayın:
Basit olması için otomatik dağıtımları etkinleştirebilirsiniz. GitHub deponuza bir taahhütte bulunduğunuzda dağıtılacaktır:
Şimdi MongoDB'yi kaynak olarak eklemeliyiz. Kaynaklar sekmesine gidin ve "Daha fazla eklenti bul"u tıklayın. (Kişisel olarak mLab mongoDB kullanıyorum.)
Yükleyin ve "Tedarik edilecek uygulama" giriş kutusuna uygulamanızın adını girin:
Son olarak, projemizin kök düzeyinde, Heroku başlatıldığında uygulama tarafından yürütülen komutları belirten Procfile
adlı bir dosya oluşturmalıyız.
Procfile
bu kadar basit:
web: node index.js
Dosyayı oluşturun ve taahhüt edin. Taahhüdü zorladığınızda, Heroku, https://[YOUR_UNIQUE_APP_NAME].herokuapp.com/
olarak erişilebilecek olan uygulamanızı otomatik olarak dağıtacaktır.
Çalışıp çalışmadığını kontrol etmek için localhost
gönderdiğimiz isteklerin aynılarını gönderebiliriz.
Node.js, Express, MongoDB, Cron ve Heroku: Yolun Yarısındayız!
Üçüncü taahhüdümüzden sonra, depomuz böyle görünecek.
Şimdiye kadar, arka uçumuza Node.js/Express tabanlı REST API'sini, GitHub'ın API'sini tüketen güncelleyiciyi ve onu etkinleştirmek için bir cron işi uyguladık. Ardından, daha sonra sürekli entegrasyon için kancalı Heroku kullanan statik web içeriği oluşturucumuz için veri sağlayacak olan arka uçumuzu dağıttık. Artık ön ucu uyguladığımız ve uygulamayı tamamladığımız ikinci bölüme hazırsınız!