この一連の記事では、静的コンテンツWebサイトのプロトタイプを開発します。人気のあるGitHubリポジトリの最新リリースを追跡するために、毎日更新される単純な静的HTMLページを生成します。静的Webページ生成フレームワークには、それを実現するための優れた機能があります。最も人気のあるGatsby.jsを使用します。
Gatsbyでは、バックエンド(サーバーレス)を使用せずにフロントエンドのデータを収集する方法はたくさんありますが、 ヘッドレスCMSプラットフォーム そして Gatsbyソースプラグイン その中で。ただし、GitHubリポジトリとその最新リリースに関する基本情報を保存するためのバックエンドを実装します。したがって、バックエンドとフロントエンドの両方を完全に制御できます。
また、アプリケーションの毎日の更新をトリガーするための一連のツールについても説明します。手動で、または特定のイベントが発生したときにトリガーすることもできます。
フロントエンドアプリケーションはNetlifyで実行され、バックエンドアプリケーションは無料プランを使用してHerokuで動作します。 定期的に寝ます :「誰かがアプリにアクセスすると、dynoマネージャーは自動的にWeb dynoを起動して、Webプロセスタイプを実行します。」だから、私たちはそれを介してそれを起こすことができます AWS Lambda およびAWSCloudWatch。これを書いている時点で、これはプロトタイプを24時間年中無休でオンラインにするための最も費用効果の高い方法です。
これらの記事を1つのトピックに集中させるために、認証、検証、スケーラビリティ、またはその他の一般的なトピックについては説明しません。この記事のコーディング部分は、可能な限り単純になります。プロジェクトの構造と正しいツールセットの使用がより重要です。
シリーズのこの最初のパートでは、バックエンドアプリケーションを開発してデプロイします。に 第二部 、フロントエンドアプリケーションを開発してデプロイし、デイリービルドをトリガーします。
バックエンドアプリケーションはNode.jsで記述され(必須ではありませんが、簡単にするため)、すべての通信はRESTAPIを介して行われます。このプロジェクトでは、フロントエンドからデータを収集しません。 (あなたがそれをすることに興味があるなら、見てください ギャツビーフォーム 。)
まず、MongoDBのリポジトリコレクションのCRUD操作を公開する単純なRESTAPIバックエンドを実装することから始めます。次に、このコレクション内のドキュメントを更新するために、GitHub API v4(GraphQL)を使用するcronジョブをスケジュールします。次に、これらすべてをHerokuクラウドにデプロイします。最後に、cronジョブの最後にフロントエンドの再構築をトリガーします。
2番目の記事では、の実装に焦点を当てます createPages
火 。バックエンドからすべてのリポジトリを収集し、すべてのリポジトリのリストと、返された各リポジトリドキュメントのページを含む単一のホームページを生成します。次に、 フロントエンドをNetlifyにデプロイする 。
アプリケーションがスリープしない場合、この部分は必須ではありません。それ以外の場合は、リポジトリの更新時にバックエンドが稼働していることを確認する必要があります。解決策として、毎日の更新の10分前にAWS CloudWatchでcronスケジュールを作成し、それをトリガーとしてGET
にバインドできます。 AWSLambdaのメソッド。バックエンドアプリケーションにアクセスすると、Herokuインスタンスがウェイクアップします。詳細は2番目の記事の最後にあります。
実装するアーキテクチャは次のとおりです。
SpringBootセキュリティトークンベースの認証例
この記事の読者は、次の分野の知識があると思います。
あなたが知っているならそれも良いです:
バックエンドの実装について詳しく見ていきましょう。これを2つのタスクに分割します。 1つ目は、REST APIエンドポイントを準備し、それらをリポジトリコレクションにバインドすることです。 2つ目は、GitHubAPIを使用してコレクションを更新するcronジョブを実装することです。
WebアプリケーションフレームワークにはExpressを使用し、MongoDB接続にはMongooseを使用します。 ExpressとMongooseに精通している場合は、手順2にスキップできる場合があります。
(一方、Expressに精通している必要がある場合は、チェックアウトできます。 公式Expressスターターガイド ;マングースに慣れていない場合は、 マングースの公式スターターガイド 役立つはずです。)
プロジェクトのファイル/フォルダ階層は単純になります。
さらに詳細に:
env.config.js
環境変数構成ファイルですroutes.config.js
残りのエンドポイントをマッピングするためのものですrepository.controller.js
リポジトリモデルで作業するためのメソッドが含まれていますrepository.model.js
リポジトリとCRUD操作のMongoDBスキーマが含まれていますindex.js
イニシャライザクラスですpackage.json
依存関係とプロジェクトプロパティが含まれています実行npm install
(または、Yarnがインストールされている場合は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
:プロジェクトのホームURLrepositoryDescription
:リポジトリの説明avatarUrl
:プロジェクトオーナーのアバターのURLconst 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
に送信します(例:PostmanまたはcURLを使用):
役職: http:// localhost:3000 / repository
体:
{ 'owner' : 'facebook', 'name' : 'react' }
取得する: http:// localhost:3000 / repository
取得する: http:// localhost:3000 / repository /:id
パッチ: http:// localhost:3000 / repository /:id
体:
{ 'owner' : 'facebook', 'name' : 'facebook-android-sdk' }
これが機能したら、更新を自動化するときが来ました。
このパートでは、データベースに挿入したGitHubリポジトリを更新するために、単純なcronジョブ(UTCの深夜に開始)を構成します。 owner
のみを追加しましたおよびname
上記の例のパラメータのみですが、これら2つのフィールドは、特定のリポジトリに関する一般的な情報にアクセスするのに十分です。
ソフトウェアプロジェクト管理におけるコスト見積もり
データを更新するには、GitHubAPIを使用する必要があります。この部分については、よく知っていることが最善です GraphQL そして GitHubAPIのv4 。
私たちもする必要があります GitHubアクセストークンを作成する 。そのために最低限必要なスコープは次のとおりです。
これによりトークンが生成され、それを使用してGitHubにリクエストを送信できます。
それでは、コードに戻りましょう。
package.json
に2つの新しい依存関係があります。
'axios': '^0.18.0'
はHTTPクライアントであるため、GitHubAPIにリクエストを送信できます'cron': '^1.7.0'
cronジョブスケジューラですいつものように、npm install
を実行しますまたはyarn
依存関係を追加した後。
config.js
にも2つの新しいプロパティが必要です。
'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
にあります。簡潔にするために、すべてのリポジトリを一度に更新するように設計します。ただし、リポジトリの数が多い場合は、 GitHubのAPIのリソース制限 。その場合は、これを変更して、限られたバッチで実行し、時間をかけて分散させる必要があります。
更新機能の一括実装は次のようになります。
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クエリを生成し、GitHubAPIを呼び出します。そのリクエストからの応答は、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); }); }
2回目のコミットの後、実装します GitHubリポジトリから毎日更新を取得するcronスケジューラ 。
バックエンドはほぼ完成です。ただし、フロントエンドを実装した後に最後のステップを実行する必要があるため、次の記事で説明します。
このステップでは、アプリケーションをHerokuにデプロイします。 彼らと一緒にアカウントを設定する必要があります まだ持っていない場合。 HerokuアカウントをGitHubにバインドすると、継続的デプロイがはるかに簡単になります。そのために、 GitHubでプロジェクトをホストしています 。
Herokuアカウントにログインした後、ダッシュボードから新しいアプリを追加します。
一意の名前を付けます。
デプロイメントセクションにリダイレクトされます。デプロイ方法としてGitHubを選択し、リポジトリを検索して、[接続]ボタンをクリックします。
簡単にするために、自動展開を有効にすることができます。 GitHubリポジトリにコミットをプッシュするたびにデプロイされます。
次に、MongoDBをリソースとして追加する必要があります。 [リソース]タブに移動し、[その他のアドオンを検索]をクリックします。 (私は個人的にmLab mongoDBを使用しています。)
Excelでデータを変換する方法
それをインストールし、「プロビジョニングするアプリ」入力ボックスにアプリの名前を入力します。
最後に、Procfile
という名前のファイルを作成する必要がありますプロジェクトのルートレベルで、Herokuの起動時にアプリによって実行されるコマンドを指定します。
私たちのProcfile
これと同じくらい簡単です:
web: node index.js
ファイルを作成してコミットします。コミットをプッシュすると、Herokuはアプリケーションを自動的にデプロイします。アプリケーションにはhttps://[YOUR_UNIQUE_APP_NAME].herokuapp.com/
としてアクセスできます。
動作しているかどうかを確認するために、localhost
に送信したのと同じリクエストを送信できます。
3回目のコミットの後、 これは私たちのリポジトリがどのように見えるかです 。
これまでに、 Node.js バックエンドの/ ExpressベースのRESTAPI、GitHubのAPIを使用するアップデーター、およびそれをアクティブ化するためのcronジョブ。次に、バックエンドをデプロイしました。これにより、後でデータが提供されます。 静的Webコンテンツジェネレータ 継続的インテグレーションのためのフック付きのHerokuを使用します。これで準備が整いました 第二部 、フロントエンドを実装してアプリを完成させます!
関連: Node.js開発者が犯す最も一般的な間違いトップ10公開後、静的Webページにはすべてのセッションで同じデータが含まれます。動的なWebページでは、データをその場で更新できます。
Node.jsは軽量、高速、スケーラブル、オープンソースであり、コミュニティによって十分にサポートされています。
Node.jsは、JavaScriptを使用してスケーラブルで軽量な非同期のイベント駆動型Webアプリケーションを構築するためのバックエンドランタイム環境として機能します。
Node.jsは、ブラウザで通常使用されるのと同じ言語(JavaScript)をサーバー側に使用します。軽量で、リクエストの処理中にノンブロッキングI / O操作を使用するように設計されています。
人気のあるMEANスタック(MongoDB、Express.js、Angular、Node.js)のメンバーとして、Node.jsは、JavaScriptを使用して高性能でスケーラブルなWebアプリケーションを開発するために重要です。
GraphQLの利点には、サーバーから必要なものだけを収集すること、1つのリクエストで複数のリソースを取得すること、APIが自己文書化されていることなどがあります。
GraphQLは、ラピッドプロトタイピングと本番環境への展開を可能にします。さらに、すべてのリソースに単一のエンドポイントを使用するため、クライアントサーバー通信が容易になります。
Herokuは、アプリの起動とスケーリングの合理化に重点を置いたクラウドプラットフォームです。