Arka Uç: Statik Site Güncellemeleri için Gatsby.js ve Node.js'yi Kullanma

Yayınlanan: 2022-03-11

Bu 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:

AWS Lambda ve CloudWatch'in, GitHub API'sini kullanarak günlük güncellemeler alan ve ardından statik sayfalarını güncellemek ve Netlify'a dağıtmak için arka uç API'lerini kullanan Gatsby tabanlı ön ucu oluşturan Node.js arka ucuna ping attığını gösteren mimari diyagram. Arka uç ayrıca ücretsiz bir planla Heroku'ya dağıtılır.

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:

config, controller, model ve node_modules klasörlerinin yanı sıra index.js ve package.json gibi birkaç standart kök dosyasını gösteren proje kökünün bir klasör listesi. İlk üç klasörün dosyaları, belirli bir klasör içindeki her dosya adında klasör adının yinelenmesine ilişkin adlandırma kuralına uyar.

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:

İhtiyacımız olan GitHub belirteç kapsamları repo:status, repo_deployment, public_repo, read:org ve read:user.

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:

Heroku panosundaki Yeni menüden "Yeni uygulama oluştur" seçimi.

Benzersiz bir ad verin:

Uygulamanızı Heroku'da adlandırma.

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:

Yeni GitHub deponuzu Heroku uygulamanıza bağlama.

Basit olması için otomatik dağıtımları etkinleştirebilirsiniz. GitHub deponuza bir taahhütte bulunduğunuzda dağıtılacaktır:

Heroku'da otomatik dağıtımları etkinleştirme.

Ş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.)

Heroku uygulamanıza bir MongoDB kaynağı ekleme.

Yükleyin ve "Tedarik edilecek uygulama" giriş kutusuna uygulamanızın adını girin:

Heroku'daki mLab MongoDB eklenti sağlama sayfası.

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!

İlgili: Node.js Geliştiricilerinin Yaptığı En Yaygın 10 Hata