В тази серия статии ще разработим прототип на уебсайт със статично съдържание. Той ще генерира ежедневно актуализирани, прости статични HTML страници за популярни хранилища на GitHub, за да проследява последните им издания. Статичните рамки за генериране на уеб страници имат чудесни функции за постигане на това - ще използваме Gatsby.js, един от най-популярните.
В Gatsby има много начини за събиране на данни за преден край, без да има заден край (без сървър), Безглави CMS платформи и Изходни приставки за Gatsby между тях. Но ние ще приложим back end, за да съхраняваме основна информация за хранилищата на GitHub и техните най-нови версии. По този начин ще имаме пълен контрол както върху нашия заден край, така и върху предния край.
Освен това ще разгледам набор от инструменти за задействане на ежедневна актуализация на вашето приложение. Можете също да го задействате ръчно или когато се случи някакво конкретно събитие.
Нашето приложение отпред ще се изпълнява на Netlify, а приложението отзад ще работи върху Heroku, използвайки безплатен план. Ще спи периодично : „Когато някой осъществи достъп до приложението, мениджърът на dyno автоматично ще събуди уеб dyno, за да стартира типа уеб процес.“ Така че можем да го събудим чрез AWS Ламбда и AWS CloudWatch. Към момента на писането това е най-рентабилният начин да имате прототип онлайн 24/7.
За да запазя тези статии фокусирани върху една тема, няма да обхващам удостоверяване, валидиране, мащабируемост или други общи теми. Кодиращата част на тази статия ще бъде възможно най-опростена. Структурата на проекта и използването на правилния набор от инструменти са по-важни.
В тази първа част от поредицата ще разработим и внедрим нашето приложение отзад. В втората част , ние ще разработим и внедрим нашето приложение отпред и ще задействаме ежедневни компилации.
Back-end приложението ще бъде написано в Node.js (не е задължително, но за улеснение) и всички комуникации ще бъдат през REST API. В този проект няма да събираме данни от предния край. (Ако се интересувате от това, погледнете Форми на Гетсби .)
Първо, ще започнем с внедряването на прост заден край на REST API, който разкрива CRUD операциите на колекцията от хранилища в нашата MongoDB. След това ще насрочим cron работа, която използва GitHub API v4 (GraphQL), за да актуализираме документи в тази колекция. След това ще разположим всичко това в облака Heroku. И накрая, ще задействаме възстановяване на предния край в края на нашата cron работа.
език за програмиране c++
Във втората статия ще се съсредоточим върху изпълнението на createPages
ПОЖАР . Ще съберем всички хранилища от задната страна и ще генерираме една начална страница, която съдържа списък на всички хранилища, плюс страница за всеки върнат документ на хранилището. Тогава ще го направим разположим нашия преден край в Netlify .
Тази част не е задължителна, ако приложението ви не спи. В противен случай трябва да сте сигурни, че задният ви край работи и работи по време на актуализиране на хранилищата. Като решение можете да създадете cron график на AWS CloudWatch 10 минути преди ежедневната си актуализация и да го обвържете като задействащ файл за вашия GET
метод в AWS Lambda. Достъпът до back-end приложението ще събуди екземпляра Heroku. Повече подробности ще има в края на втората статия.
Ето архитектурата, която ще приложим:
Предполагам, че читателите на тази статия имат познания в следните области:
Също така е добре, ако знаете:
Нека да се потопим в изпълнението на задния край. Ще го разделим на две задачи. Първият подготвя крайни точки на REST API и ги обвързва с нашата колекция от хранилища. Второто е внедряване на cron работа, която консумира GitHub API и актуализира колекцията.
Ще използваме Express за нашата рамка за уеб приложения и Mongoose за нашата връзка MongoDB. Ако сте запознати с Express и Mongoose, може да преминете към стъпка 2.
(От друга страна, ако имате нужда от повече познания с Express, можете да проверите официалното ръководство за стартиране на Express ; ако не сте на Mongoose, официалното начално ръководство за Mongoose трябва да е полезно.)
Йерархията на файла / папката на нашия проект ще бъде проста:
В повече детайли:
env.config.js
е конфигурационният файл на променливите на средатаroutes.config.js
е за картографиране на останалите крайни точкиrepository.controller.js
съдържа методи за работа по нашия модел на хранилищеrepository.model.js
съдържа MongoDB схемата на хранилището и CRUD операциитеindex.js
е клас на инициализаторpackage.json
съдържа зависимости и свойства на проектаИзпълнение npm install
(или yarn
, ако имате инсталирана прежда) след добавяне на тези зависимости към 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' } // ... }
Нашите env.config.js
файлът има само port
, environment
(dev
или prod
) и mongoDbUri
свойства за сега:
module.exports = ;
routes.config.js
съдържа картографиране на заявки и ще извика съответния метод на нашия контролер:
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
файл е нашият сервизен слой. Неговата отговорност е да извика съответния метод от нашия модел на хранилище:
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
обработва връзката MongoDb и операциите CRUD за модела на хранилището. Полетата на модела са:
owner
: Собственик на хранилището (компания или потребител)name
: Името на хранилищетоcreatedAt
: Дата на последното създаване на изданиетоresourcePath
: Последният път на издаванеtagName
: Последният таг за освобождаванеreleaseDescription
: Бележки по изданиетоhomepageUrl
: Началният URL адрес на проектаrepositoryDescription
: Описание на хранилищетоavatarUrl
: URL адрес на аватара на собственика на проекта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 }); };
Ето какво имаме след първия си ангажимент: Връзка MongoDB и нашите REST операции .
Можем да стартираме нашето приложение със следната команда:
node index.js
За тестване изпратете заявки до localhost:3000
(използвайки например пощальон или cURL):
Публикация: http: // localhost: 3000 / хранилища
Тяло:
Тестването на прототипа на мобилно приложение може да се направи ръчно или какво?
{ 'owner' : 'facebook', 'name' : 'react' }
Вземете: http: // localhost: 3000 / хранилища
Вземете: http: // localhost: 3000 / хранилища /:документ за самоличност
Кръпка: http: // localhost: 3000 / хранилища /:документ за самоличност
Тяло:
{ 'owner' : 'facebook', 'name' : 'facebook-android-sdk' }
С тази работа е време да автоматизирате актуализациите.
В тази част ще конфигурираме проста задача cron (която ще започне в полунощ UTC), за да актуализираме хранилищата GitHub, които вмъкнахме в нашата база данни. Добавихме само owner
и name
параметри само в нашия пример по-горе, но тези две полета са ни достатъчни за достъп до обща информация за дадено хранилище.
За да актуализираме данните си, трябва да използваме API на GitHub. За тази част е най-добре да сте запознати GraphQL и v4 на API на GitHub .
Ние също трябва създайте GitHub маркер за достъп . Минималният необходим обхват за това е:
Това ще генерира токен и ние можем да изпращаме заявки до GitHub с него.
Сега да се върнем към нашия код.
Имаме две нови зависимости в package.json
:
'axios': '^0.18.0'
е HTTP клиент, така че можем да отправяме заявки към GitHub API'cron': '^1.7.0'
е cron планировчик на работаКакто обикновено, изпълнете npm install
или yarn
след добавяне на зависимости.
Ще ни трябват и две нови свойства в config.js
също:
'githubEndpoint': 'https://api.github.com/graphql'
'githubAccessToken': process.env.GITHUB_ACCESS_TOKEN
(ще трябва да зададете променливата GITHUB_ACCESS_TOKEN
среда с вашия собствен личен маркер за достъп)Създайте нов файл под controller
папка с името cron.controller.js
. Той просто ще извика updateResositories
метод на repository.controller.js
по насрочено време:
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'); };
Окончателните промени за тази част ще бъдат в repository.controller.js
. За краткост ще го проектираме да актуализира всички хранилища наведнъж. Но ако имате голям брой хранилища, може да надхвърлите ограничения на ресурсите на API на GitHub . Ако случаят е такъв, ще трябва да промените това, за да се изпълнява в ограничени партиди, разпределени във времето.
Изпълнението наведнъж на функционалността за актуализация ще изглежда така:
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'); }); };
Накрая ще извикаме крайната точка и ще актуализираме модела на хранилището.
getLatestRelease
функция ще генерира GraphQL заявка и ще извика GitHub API. След това отговорът от тази заявка ще бъде обработен в 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); }); }
След втория ни ангажимент ще изпълним cron планировчик, за да получавате ежедневни актуализации от нашите хранилища на GitHub .
Почти приключихме със задния край. Но последната стъпка там трябва да бъде направена след внедряването на предния край, така че ще я разгледаме в следващата статия.
В тази стъпка ще разгърнем приложението си в Heroku, така че ще трябва да създадете акаунт с тях ако вече нямате такъв. Ако свържем акаунта си в Heroku с GitHub, ще бъде много по-лесно да имаме непрекъснато внедряване. До този край, Аз съм домакин на моя проект в GitHub .
какво е надолу кръг
След като влезете в акаунта си в Heroku, добавете ново приложение от таблото за управление:
Дайте му някакво уникално име:
Ще бъдете пренасочени към раздел за внедряване. Изберете GitHub като метод за внедряване, потърсете своето хранилище, след което щракнете върху бутона „Свързване“:
За улеснение можете да активирате автоматичното разполагане. Той ще се разположи всеки път, когато натиснете ангажимент към вашия GitHub репо:
Сега трябва да добавим MongoDB като ресурс. Отидете в раздела Ресурси и щракнете върху „Намиране на още добавки“. (Аз лично използвам mLab mongoDB.)
трябва ли да научите c преди c++
Инсталирайте го и въведете името на приложението си в полето за въвеждане на „Приложение за предоставяне до“:
И накрая, трябва да създадем файл с име Procfile
на основното ниво на нашия проект, който определя командите, които се изпълняват от приложението, когато Heroku го стартира.
Нашите Procfile
е толкова просто, колкото това:
web: node index.js
Създайте файла и го ангажирайте. След като натиснете фиксацията, Heroku автоматично ще разгърне приложението ви, което ще бъде достъпно като https://[YOUR_UNIQUE_APP_NAME].herokuapp.com/
За да проверим дали работи, можем да изпратим същите заявки, които изпратихме до localhost
.
След третия ни ангажимент, така ще изглежда нашето репо .
Досега внедрихме Node.js / Express-базиран REST API на нашия гръб, актуализаторът, който използва API на GitHub, и cron задача, за да го активира. След това внедрихме нашия заден край, който по-късно ще предостави данни за нашия генератор на статично уеб съдържание използване на Heroku с кука за непрекъсната интеграция. Сега сте готови за втората част , където ние прилагаме предния край и попълваме приложението!
Свързани: Топ 10 на най-често срещаните грешки, които Node.js правят разработчицитеСлед публикуването статичните уеб страници съдържат едни и същи данни за всички сесии. В динамичните уеб страници данните могат да се актуализират в движение.
Node.js е лек, бърз, мащабируем, с отворен код и добре поддържан от своята общност.
Node.js служи като среда за изпълнение на back-end за изграждане на мащабируеми, леки, асинхронни, управлявани от събития уеб приложения с JavaScript.
Node.js използва същия език (JavaScript) за сървърната страна, който обикновено се използва в браузъра. Той е лек и е проектиран да използва неблокиращи I / O операции, докато заявките се обработват.
Като член на популярния стек MEAN - MongoDB, Express.js, Angular и Node.js - Node.js е важен за разработването на високоефективни, мащабируеми уеб приложения с JavaScript.
Някои от предимствата на GraphQL включват събиране от сървъра само на това, от което се нуждаете, получаване на множество ресурси в една заявка и факта, че неговите API са самодокументирани.
GraphQL позволява бързо прототипиране и внедряване на производството. Освен това той използва една крайна точка за всички ресурси, което улеснява комуникацията клиент-сървър.
Heroku е облачна платформа, фокусирана върху рационализирането на стартирането и мащабирането на приложения.