ในชุดบทความนี้เราจะพัฒนาต้นแบบเว็บไซต์เนื้อหาคงที่ จะสร้างหน้า HTML แบบคงที่ที่อัปเดตทุกวันสำหรับที่เก็บ GitHub ยอดนิยมเพื่อติดตามการเผยแพร่ล่าสุด เฟรมเวิร์กการสร้างหน้าเว็บแบบคงที่มีคุณสมบัติที่ยอดเยี่ยมในการบรรลุเป้าหมายนั้นเราจะใช้ Gatsby.js ซึ่งเป็นหนึ่งในเฟรมเวิร์กที่ได้รับความนิยมมากที่สุด
ใน Gatsby มีหลายวิธีในการรวบรวมข้อมูลสำหรับส่วนหน้าโดยไม่ต้องมีส่วนหลัง (ไร้เซิร์ฟเวอร์) แพลตฟอร์ม CMS หัวขาด และ ปลั๊กอินแหล่งที่มาของ Gatsby ในหมู่พวกเขา แต่เราจะใช้แบ็คเอนด์เพื่อจัดเก็บข้อมูลพื้นฐานเกี่ยวกับที่เก็บ GitHub และรีลีสล่าสุด ดังนั้นเราจะสามารถควบคุมทั้งส่วนหลังและส่วนหน้าได้อย่างเต็มที่
นอกจากนี้ฉันจะพูดถึงชุดเครื่องมือเพื่อเริ่มการอัปเดตแอปพลิเคชันของคุณทุกวัน คุณยังสามารถทริกเกอร์ได้ด้วยตนเองหรือเมื่อใดก็ตามที่มีเหตุการณ์เฉพาะเกิดขึ้น
แอปพลิเคชันส่วนหน้าของเราจะทำงานบน Netlify และแอปพลิเคชันส่วนหลังจะทำงานบน Heroku โดยใช้แผนบริการฟรี มันจะนอนเป็นระยะ :“ เมื่อมีคนเข้าถึงแอปตัวจัดการ dyno จะปลุกเว็บ dyno โดยอัตโนมัติเพื่อเรียกใช้ประเภทกระบวนการของเว็บ” ดังนั้นเราสามารถปลุกมันผ่าน AWS Lambda และ AWS CloudWatch จากการเขียนนี้นี่เป็นวิธีที่คุ้มค่าที่สุดในการสร้างต้นแบบออนไลน์ตลอด 24 ชั่วโมงทุกวัน
เพื่อให้บทความเหล่านี้มุ่งเน้นไปที่หัวข้อเดียวฉันจะไม่กล่าวถึงการพิสูจน์ตัวตนการตรวจสอบความสามารถในการปรับขนาดหรือหัวข้อทั่วไปอื่น ๆ ส่วนการเขียนโค้ดของบทความนี้จะง่ายที่สุด โครงสร้างของโครงการและการใช้ชุดเครื่องมือที่ถูกต้องมีความสำคัญมากขึ้น
วิธีการทำงานทางไกลจากบ้าน
ในส่วนแรกของซีรีส์นี้เราจะพัฒนาและปรับใช้แอปพลิเคชันแบ็คเอนด์ของเรา ใน ส่วนที่สอง เราจะพัฒนาและปรับใช้แอปพลิเคชันส่วนหน้าของเราและเรียกใช้งานสร้างรายวัน
แอปพลิเคชันแบ็คเอนด์จะเขียนใน Node.js (ไม่บังคับ แต่เพื่อความง่าย) และการสื่อสารทั้งหมดจะอยู่บน REST API เราจะไม่รวบรวมข้อมูลจากส่วนหน้าในโครงการนี้ (หากคุณสนใจที่จะทำเช่นนั้นโปรดดูที่ แบบฟอร์ม Gatsby .)
ขั้นแรกเราจะเริ่มต้นด้วยการใช้แบ็คเอนด์ REST API แบบง่ายๆที่เปิดเผยการดำเนินการ CRUD ของคอลเลกชันที่เก็บใน MongoDB ของเรา จากนั้นเราจะกำหนดเวลางาน cron ที่ใช้ GitHub API v4 (GraphQL) เพื่ออัปเดตเอกสารในคอลเล็กชันนี้ จากนั้นเราจะปรับใช้ทั้งหมดนี้กับระบบคลาวด์ Heroku ในที่สุดเราจะเริ่มสร้างส่วนหน้าใหม่เมื่อสิ้นสุดงาน cron ของเรา
ในบทความที่สองเราจะเน้นไปที่การใช้งานไฟล์ createPages
ไฟ . เราจะรวบรวมที่เก็บทั้งหมดจากส่วนหลังและจะสร้างโฮมเพจเดียวที่มีรายการของที่เก็บทั้งหมดรวมทั้งเพจสำหรับเอกสารที่เก็บแต่ละรายการที่ส่งคืน แล้วเราจะ ปรับใช้ส่วนหน้าของเรากับ Netlify .
ส่วนนี้ไม่บังคับหากแอปพลิเคชันของคุณไม่อยู่ในโหมดสลีป มิฉะนั้นคุณต้องแน่ใจว่าส่วนหลังของคุณเปิดใช้งานอยู่ในขณะที่อัปเดตที่เก็บ ในการแก้ปัญหาคุณสามารถสร้างกำหนดการ cron บน AWS CloudWatch 10 นาทีก่อนการอัปเดตประจำวันของคุณและผูกเป็นทริกเกอร์กับ GET
ของคุณ วิธีการใน AWS Lambda การเข้าถึงแอปพลิเคชันส่วนหลังจะปลุกอินสแตนซ์ Heroku รายละเอียดเพิ่มเติมจะอยู่ในตอนท้ายของบทความที่สอง
นี่คือสถาปัตยกรรมที่เราจะนำไปใช้:
ฉันคิดว่าผู้อ่านบทความนี้มีความรู้ในด้านต่อไปนี้:
นอกจากนี้ยังเป็นการดีหากคุณทราบ:
มาเจาะลึกการใช้งานส่วนหลังกัน เราจะแบ่งออกเป็นสองงาน รายการแรกกำลังเตรียมจุดสิ้นสุด REST API และเชื่อมโยงเข้ากับคอลเล็กชันที่เก็บของเรา อย่างที่สองคือการใช้งาน cron ที่ใช้ GitHub API และอัปเดตคอลเล็กชัน
เราจะใช้ Express สำหรับเฟรมเวิร์กแอปพลิเคชันเว็บและพังพอนสำหรับการเชื่อมต่อ MongoDB ของเรา หากคุณคุ้นเคยกับ Express และ Mongoose คุณสามารถข้ามไปยังขั้นตอนที่ 2 ได้
(ในทางกลับกันหากคุณต้องการความคุ้นเคยกับ Express มากขึ้นคุณสามารถตรวจสอบได้ คู่มือเริ่มต้น Express อย่างเป็นทางการ ; ถ้าคุณไม่ได้อยู่กับพังพอน คู่มือเริ่มต้นพังพอนอย่างเป็นทางการ น่าจะเป็นประโยชน์)
ลำดับชั้นไฟล์ / โฟลเดอร์ของโปรเจ็กต์ของเราจะเรียบง่าย:
รายละเอียดเพิ่มเติม:
env.config.js
คือไฟล์คอนฟิกูเรชันตัวแปรสภาพแวดล้อมroutes.config.js
มีไว้สำหรับการแมปปลายทางที่เหลือrepository.controller.js
มีวิธีการทำงานกับโมเดลพื้นที่เก็บข้อมูลของเราrepository.model.js
มีสคีมา MongoDB ของที่เก็บและการดำเนินการ CRUDindex.js
เป็นคลาส initializerpackage.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
: 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 / ที่เก็บ /: id
บัตรเครดิตรั่วที่ทำงาน
ปะ: http: // localhost: 3000 / ที่เก็บ /: id
ร่างกาย:
{ 'owner' : 'facebook', 'name' : 'facebook-android-sdk' }
เมื่อได้ผลแล้วก็ถึงเวลาอัปเดตอัตโนมัติ
ในส่วนนี้เราจะกำหนดค่างาน cron อย่างง่าย (ซึ่งจะเริ่มในเวลาเที่ยงคืน UTC) เพื่ออัปเดตที่เก็บ GitHub ที่เราแทรกลงในฐานข้อมูลของเรา เราเพิ่มเฉพาะ owner
และ name
พารามิเตอร์ในตัวอย่างของเราด้านบนเท่านั้น แต่ทั้งสองฟิลด์นี้เพียงพอสำหรับเราในการเข้าถึงข้อมูลทั่วไปเกี่ยวกับที่เก็บที่กำหนด
ในการอัปเดตข้อมูลของเราเราต้องใช้ GitHub API สำหรับส่วนนี้ควรทำความคุ้นเคยกับส่วนนี้ GraphQL และ v4 ของ GitHub API .
เรายังต้อง สร้างโทเค็นการเข้าถึง 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 . ในกรณีนี้คุณจะต้องแก้ไขเพื่อให้ทำงานเป็นกลุ่มที่ จำกัด และกระจายออกไปเมื่อเวลาผ่านไป
การใช้งานฟังก์ชั่นการอัปเดตทั้งหมดในครั้งเดียวจะมีลักษณะดังนี้:
อิออน 2 และเชิงมุม 2
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)
ติดตั้งและป้อนชื่อแอปของคุณในช่องป้อน“ แอปเพื่อจัดสรรให้”:
เป็นอาหารทั้งหมดที่เป็นของ walmart
สุดท้ายเราต้องสร้างไฟล์ชื่อ Procfile
ที่ระดับรูทของโปรเจ็กต์ของเราซึ่งระบุคำสั่งที่แอพเรียกใช้งานเมื่อ Heroku เริ่มต้นขึ้น
ของเรา Procfile
ทำได้ง่ายๆดังนี้:
web: node index.js
สร้างไฟล์และคอมมิต เมื่อคุณกดคอมมิต Heroku จะปรับใช้แอปพลิเคชันของคุณโดยอัตโนมัติซึ่งสามารถเข้าถึงได้ในชื่อ https://[YOUR_UNIQUE_APP_NAME].herokuapp.com/
เพื่อตรวจสอบว่าใช้งานได้หรือไม่เราสามารถส่งคำขอเดียวกันกับที่เราส่งไปที่ localhost
หลังจากการกระทำครั้งที่สามของเรา นี่คือลักษณะที่ repo ของเราจะเป็นอย่างไร .
จนถึงขณะนี้เราได้ใช้งานไฟล์ โหนด js / Express-based REST API ที่แบ็คเอนด์ของเราตัวอัปเดตที่ใช้ API ของ GitHub และงาน cron เพื่อเปิดใช้งาน จากนั้นเราได้ปรับใช้งานส่วนหลังของเราซึ่งจะให้ข้อมูลในภายหลัง ตัวสร้างเนื้อหาเว็บแบบคงที่ ใช้ Heroku กับตะขอสำหรับการรวมอย่างต่อเนื่อง ตอนนี้คุณพร้อมแล้วสำหรับ ส่วนที่สอง ที่เราใช้ส่วนหน้าและทำให้แอปสมบูรณ์!
ที่เกี่ยวข้อง: ข้อผิดพลาดทั่วไป 10 อันดับแรกที่นักพัฒนา Node.js ทำหลังจากเผยแพร่แล้วหน้าเว็บแบบคงที่จะมีข้อมูลเดียวกันสำหรับทุกเซสชัน ในหน้าเว็บแบบไดนามิกสามารถอัปเดตข้อมูลได้ทันที
Node.js มีน้ำหนักเบารวดเร็วปรับขนาดได้โอเพ่นซอร์สและได้รับการสนับสนุนอย่างดีจากชุมชน
Node.js ทำหน้าที่เป็นสภาพแวดล้อมรันไทม์แบ็คเอนด์สำหรับการสร้างเว็บแอปพลิเคชันที่ปรับขนาดได้น้ำหนักเบาแบบอะซิงโครนัสที่ขับเคลื่อนด้วยเหตุการณ์ด้วย JavaScript
Node.js ใช้ภาษาเดียวกัน (JavaScript) สำหรับฝั่งเซิร์ฟเวอร์ที่ปกติใช้ในเบราว์เซอร์ มีน้ำหนักเบาและออกแบบมาเพื่อใช้การดำเนินการ I / O แบบไม่ปิดกั้นในขณะที่กำลังประมวลผลคำขอ
ในฐานะสมาชิกของ MEAN stack ที่เป็นที่นิยมเช่น MongoDB, Express.js, Angular และ Node.js Node.js มีความสำคัญต่อการพัฒนาเว็บแอปพลิเคชันที่มีประสิทธิภาพสูงและปรับขนาดได้ด้วย JavaScript
ประโยชน์บางประการของ GraphQL ได้แก่ การรวบรวมเฉพาะสิ่งที่คุณต้องการจากเซิร์ฟเวอร์การได้รับทรัพยากรหลายรายการในคำขอเดียวและความจริงที่ว่า API ของมันเป็นเอกสารในตัว
GraphQL ช่วยให้สามารถสร้างต้นแบบและการใช้งานจริงได้อย่างรวดเร็ว นอกจากนี้ยังใช้ปลายทางเดียวสำหรับทรัพยากรทั้งหมดซึ่งทำให้การสื่อสารไคลเอนต์เซิร์ฟเวอร์ง่ายขึ้น
Heroku เป็นแพลตฟอร์มระบบคลาวด์ที่เน้นการเพิ่มความคล่องตัวในการเปิดตัวและการปรับขนาดแอป