<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何基于已有的 REST API 實現(xiàn) GraphQL API

          共 3734字,需瀏覽 8分鐘

           ·

          2021-02-24 17:35

          • 本文已獲得原作者的獨家授權,有想轉(zhuǎn)載的朋友們可以在后臺聯(lián)系我申請開白哦!
          • PS:歡迎掘友們向我投稿哦,被采用的文章還可以送你掘金精美周邊!
          • 原文地址:How to Implement a GraphQL API on Top of an Existing REST API
          • 原文作者:Tyler Hawkins
          • 譯文出自:掘金翻譯計劃
          • 本文永久鏈接:github.com/xitu/gold-m…
          • 譯者:samyu2000
          • 校對者:PassionPenguin, k8scat

          你的 dad jokes 放在哪兒?當然是在 dadabase 里。我們來想象一下,你是全世界最受歡迎的 dad jokes 數(shù)據(jù)庫的管理員。項目的技術概況是:使用 REST API 與數(shù)據(jù)庫通信,這種 REST API 具有搜索笑話和對笑話進行評分的功能;網(wǎng)站的訪問者可以通過一個簡單的用戶界面對每條笑話進行評分。

          最近你了解到一種新技術,它叫做 GraphQL,它具有一定的靈活性,可以精準獲取你需要的數(shù)據(jù),而且是使用單一的 API 結點。這聽上去很不錯,于是你打算在應用程序中使用這種技術。但是,你不希望對原有的 REST API 作過多的改動。能否讓你的項目同時支持 REST API 和 GraphQL API?

          在本文中,我們會討論如何基于已有的 REST API 來實現(xiàn) GraphQL API。你使用這種方法,不需要對基于原有的 REST API 框架進行調(diào)整,就可以在項目的未完成的模塊中使用 GraphQL。

          如果你想看到最終的結果,可以訪問 REST API 代碼 和 前端和 GraphQL API 代碼。還要記得瀏覽一下網(wǎng)站,那些笑話很值得看哦。

          初始架構

          項目的后臺原先是使用 Node 和 JSON Server 開發(fā)的。JSON Server 利用 Express 為一個模擬的數(shù)據(jù)庫提供了完整的 REST API,并且這個數(shù)據(jù)庫是由一個簡單的 JSON 文件生成的。前端是使用 Vanilla JS 實現(xiàn)的,并使用瀏覽器內(nèi)嵌的 Fetch API 發(fā)出 API 請求。該應用程序托管在 Heroku 上,可以方便地對它進行部署和監(jiān)控。

          我們使用的 JSON 文件含有一些笑話和評分信息。下面,我們把它完整地復制出來:

          {
          ??"jokes":?[
          ????{
          ??????"id":?1,
          ??????"content":?"I?don't?often?tell?dad?jokes,?but?when?I?do,?sometimes?he?laughs."
          ????},
          ????{
          ??????"id":?2,
          ??????"content":?"Why?was?the?scarecrow?promoted??For?being?outstanding?in?his?field."
          ????},
          ????{
          ??????"id":?3,
          ??????"content":?"What?did?the?grape?do?when?someone?stepped?on?him??He?let?out?a?little?whine."
          ????},
          ????{
          ??????"id":?4,
          ??????"content":?"Einstein,?Pascal,?and?Newton?are?playing?hide?and?seek.?Einstein?covers?his?eyes?and?begins?counting.?While?Pascal?runs?off?and?hides,?Newton?takes?out?some?chalk?and?marks?a?square?on?the?ground?with?side?lengths?of?exactly?1?meter,?then?sits?down?inside?the?square.?When?Einstein?is?finished?counting?and?sees?Newton?sitting?on?the?ground,?he?yells,?\"Ha,?I've?found?you,?Newton!\".?Newton?replies,?\"No?you?haven't!?You've?found?one?Newton?over?a?square?meter.?You've?found?Pascal!"
          ????}
          ??],
          ??"ratings":?[
          ????{?"id":?1,?"jokeId":?1,?"score":?8?},
          ????{?"id":?2,?"jokeId":?2,?"score":?3?},
          ????{?"id":?3,?"jokeId":?3,?"score":?6?},
          ????{?"id":?4,?"jokeId":?1,?"score":?7?},
          ????{?"id":?5,?"jokeId":?2,?"score":?6?},
          ????{?"id":?6,?"jokeId":?3,?"score":?4?},
          ????{?"id":?7,?"jokeId":?1,?"score":?9?},
          ????{?"id":?8,?"jokeId":?2,?"score":?10?},
          ????{?"id":?9,?"jokeId":?3,?"score":?2?},
          ????{?"id":?10,?"jokeId":?4,?"score":?10?},
          ????{?"id":?11,?"jokeId":?4,?"score":?10?},
          ????{?"id":?12,?"jokeId":?4,?"score":?10?},
          ????{?"id":?13,?"jokeId":?4,?"score":?10?},
          ????{?"id":?14,?"jokeId":?4,?"score":?10?},
          ????{?"id":?15,?"jokeId":?4,?"score":?10?}
          ??]
          }
          復制代碼

          JSON Server 系統(tǒng)把這個文件中的數(shù)據(jù)作為數(shù)據(jù)庫的初始數(shù)據(jù),接著實現(xiàn)一套 REST API,其中包括對 GET, POST, PUT, PATCH 和 DELETE 請求的支持。JSON Server 的神奇之處在于,使用這套 API 就能實現(xiàn)對 JSON 文件的修改,因此數(shù)據(jù)庫就是完全交互式的。JSON Server 不經(jīng)安裝就可以直接由 npm 腳本啟動,但為了對它進行一些配置以及端口的設置,我們可以寫下幾行代碼并運行它,代碼如下:

          const?jsonServer?=?require('json-server')
          const?server?=?jsonServer.create()
          const?router?=?jsonServer.router('db.json')
          const?middlewares?=?jsonServer.defaults()

          server.use(middlewares)
          server.use(router)
          server.listen(process.env.PORT?||?3000,?()?=>?{
          ??console.log(`???JSON?Server?is?running?on?port?${process.env.PORT?||?3000}`)
          })

          復制代碼

          欲對這個模擬的數(shù)據(jù)庫進行測試,你可以把 API 有關的倉庫克隆到本地,并運行 npm installnpm start。在瀏覽器中訪問 http://localhost:3000/jokes ,頁面會顯示所有的笑話。訪問 http://localhost:3000/ratings ,頁面會顯示所有的評分信息。

          太棒了。我們可以在瀏覽器上運行應用程序的后臺?,F(xiàn)在我們把 API 托管在 Heroku 中。首先需要安裝 Heroku 命令行工具。然后,我們可以進行這些操作:登錄,創(chuàng)建項目,推送到 Heroku 服務端,在瀏覽器中打開項目的操作界面。

          #?登錄你的?Heroku?賬戶
          heroku?login

          #?創(chuàng)建項目
          heroku?create?dad-joke-dadabase-rest-api

          #?將代碼部署到?Heroku?服務端
          git?push?heroku?master

          #?打開項目的后臺頁面
          heroku?open
          復制代碼

          看,現(xiàn)在我們把 API 發(fā)布到公網(wǎng)上了!


          構建用戶界面

          既然我們已經(jīng)部署了一個運行中的 REST API,就可以制作前端頁面,并使用 API 把這些笑話數(shù)據(jù)呈現(xiàn)在頁面上,還可以對這些笑話進行評分。下面的 HTML 頁面代碼實現(xiàn)了一個顯示笑話內(nèi)容的容器,笑話內(nèi)容由 JavaScript 代碼加載進來。


          "en">

          ??"utf-8">
          ??
          ??Dad?Joke?Dadabase
          ??do?you?keep?your?dad?jokes??In?a?dadabase?of?course!">
          ??
          ??stylesheet"?href="./style.css">


          ??

          Dad?Joke?Dadabase



          ??project">
          ????jokeContent">
          ????rateThisJokeContainer">
          ??????

          Rate?this?joke:


          ??????rateThisJokeOptions">
          ????????">radio"?id="score-1"?>1
          ????????">radio"?id="score-2"?>2
          ????????">radio"?id="score-3"?>3
          ????????">radio"?id="score-4"?>4
          ????????">radio"?id="score-5"?>5
          ????????">radio"?id="score-6"?>6
          ????????">radio"?id="score-7"?>7
          ????????">radio"?id="score-8"?>8
          ????????">radio"?id="score-9"?>9
          ????????">radio"?id="score-10"?>10
          ??????

          ????

          ????averageRating">Average?Rating:?">7.8


          ????nextJoke">See?Next?Joke

          ??


          復制代碼

          JavaScript 代碼如下。跟 REST API 交互的關鍵代碼在于兩個獲取數(shù)據(jù)的請求。第一個請求通過訪問 /jokes?_embed=ratings 獲取數(shù)據(jù)庫中所有的笑話,第二個請求是 POST 類型的,它通過訪問 /ratings 提交對某個笑話的評分。

          const?jokeContent?=?document.querySelector('.jokeContent')
          const?jokeRatingValue?=?document.querySelector('.jokeRatingValue')
          const?nextJokeButton?=?document.querySelector('#nextJoke')

          const?jokes?=?[]
          let?currentJokeIndex?=?-1

          const?displayNextJoke?=?()?=>?{
          ??currentJokeIndex++
          ??if?(currentJokeIndex?>=?jokes.length)?{
          ????currentJokeIndex?=?0
          ??}

          ??const?joke?=?jokes[currentJokeIndex]

          ??jokeContent.textContent?=?joke.content

          ??const?totalScore?=?joke.ratings.reduce(
          ????(total,?rating)?=>?(total?+=?rating.score),
          ??)
          ??const?numberOfRatings?=?joke.ratings.length
          ??const?averageRating?=?totalScore?/?numberOfRatings

          ??jokeRatingValue.textContent?=?averageRating.toFixed(1)
          }

          const?submitJokeRating?=?()?=>?{
          ??const?ratingInput?=?document.querySelector('input[]:checked')

          ??if?(ratingInput?&&?ratingInput.value)?{
          ????const?score?=?Number(ratingInput.value)
          ????const?jokeId?=?jokes[currentJokeIndex].id
          ????const?postData?=?{?jokeId,?score?}

          ????fetch('/ratings',?{
          ??????method:?'POST',
          ??????headers:?{
          ????????'Content-Type':?'application/json',
          ??????},
          ??????body:?JSON.stringify(postData),
          ????})
          ??????.then(response?=>?response.json())
          ??????.then(responseData?=>?{
          ????????const?jokeToUpdate?=?jokes.find(joke?=>?joke.id?===?responseData.jokeId)
          ????????jokeToUpdate?&&?jokeToUpdate.ratings.push(responseData)
          ??????})
          ??????.finally(()?=>?{
          ????????ratingInput.checked?=?false
          ????????displayNextJoke()
          ??????})
          ??}?else?{
          ????displayNextJoke()
          ??}
          }

          nextJokeButton.addEventListener('click',?submitJokeRating)

          fetch('/jokes?_embed=ratings')
          ??.then(response?=>?response.json())
          ??.then(data?=>?{
          ????jokes.push(...data)
          ????displayNextJoke()
          ??})

          復制代碼


          安裝并使用 Apollo Server

          這樣,我們已經(jīng)完成了項目的架構:它有一個簡單的頁面,該頁面通過 REST API 跟數(shù)據(jù)庫通信。那么,我們?nèi)绾问褂?GraphQL?使用 GraphQL 之前需要哪些準備工作呢?第一步,我們安裝 [apollo-server-express](https://www.npmjs.com/package/apollo-server-express),它是一個程序包,用于實現(xiàn) Apollo Server 和 Express 的集成。也需要安裝 [apollo-datasource-rest](https://www.npmjs.com/package/apollo-datasource-rest) 包,用于 REST API 和 Apollo Server 的集成。然后,我們來配置服務器,需要編寫以下代碼:

          const?express?=?require('express')
          const?path?=?require('path')
          const?{?ApolloServer?}?=?require('apollo-server-express')
          const?JokesAPI?=?require('./jokesAPI')
          const?RatingsAPI?=?require('./ratingsAPI')
          const?typeDefs?=?require('./typeDefs')
          const?resolvers?=?require('./resolvers')

          const?app?=?express()
          const?server?=?new?ApolloServer({
          ??typeDefs,
          ??resolvers,
          ??dataSources:?()?=>?({
          ????jokesAPI:?new?JokesAPI(),
          ????ratingsAPI:?new?RatingsAPI(),
          ??}),
          })

          server.applyMiddleware({?app?})

          app
          ??.use(express.static(path.join(__dirname,?'public')))
          ??.get('/',?(req,?res)?=>?{
          ????res.sendFile('index.html',?{?root:?'public'?})
          ??})
          ??.get('/script.js',?(req,?res)?=>?{
          ????res.sendFile('script.js',?{?root:?'public'?})
          ??})
          ??.get('/style.css',?(req,?res)?=>?{
          ????res.sendFile('style.css',?{?root:?'public'?})
          ??})

          app.listen({?port:?process.env.PORT?||?4000?},?()?=>?{
          ??console.log(`???Server?ready?at?port?${process.env.PORT?||?4000}`)
          })

          復制代碼

          你可以看到,我們配置了 Apollo Server 的三個屬性:typeDefs, resolversdataSources。其中,typeDefs 屬性包含了與我們的 GraphQL API 相關的 schema,我們在相應的包中定義笑話和評分的數(shù)據(jù)類型,以及如何查詢和更新數(shù)據(jù);resolvers 告訴服務器如何處理各種各樣的查詢和更新需求,以及如何連接數(shù)據(jù)源;最后,dataSources 大致描述了 GraphQL API 與 REST API 的關聯(lián)關系。

          下面的代碼定義了 JokeRating 數(shù)據(jù)類型,以及如何查詢和更新數(shù)據(jù)。

          const?{?gql?}?=?require('apollo-server-express')

          const?typeDefs?=?gql`
          ??type?Joke?{
          ????id:?Int!
          ????content:?String!
          ????ratings:?[Rating]
          ??}
          ??type?Rating?{
          ????id:?Int!
          ????jokeId:?Int!
          ????score:?Int!
          ??}
          ??type?Query?{
          ????joke(id:?Int!):?Joke
          ????jokes:?[Joke]
          ????rating(id:?Int!):?Rating
          ????ratings:?[Rating]
          ??}
          ??type?Mutation?{
          ????rating(jokeId:?Int!,?score:?Int!):?Rating
          ??}
          `

          module.exports?=?typeDefs
          復制代碼

          下面是 JokesAPI 類的代碼,主要定義了笑話數(shù)據(jù)創(chuàng)建、查詢、更新、刪除的方法,這些方法分別調(diào)用相應的 REST API 實施相關的數(shù)據(jù)操作。

          const?{?RESTDataSource?}?=?require('apollo-datasource-rest')

          class?JokesAPI?extends?RESTDataSource?{
          ??constructor()?{
          ????super()
          ????this.baseURL?=?'https://dad-joke-dadabase-rest-api.herokuapp.com/'
          ??}

          ??async?getJoke(id)?{
          ????return?this.get(`jokes/${id}?_embed=ratings`)
          ??}

          ??async?getJokes()?{
          ????return?this.get('jokes?_embed=ratings')
          ??}

          ??async?postJoke(jokeContent)?{
          ????return?this.post('jokes',?jokeContent)
          ??}

          ??async?replaceJoke(joke)?{
          ????return?this.put('jokes',?joke)
          ??}

          ??async?updateJoke(joke)?{
          ????return?this.patch('jokes',?{?id:?joke.id,?joke?})
          ??}

          ??async?deleteJoke(id)?{
          ????return?this.delete(`jokes/${id}`)
          ??}
          }

          module.exports?=?JokesAPI
          復制代碼

          評分數(shù)據(jù)跟笑話相似,只是在每個實例中把 “joke” 變?yōu)?“rating”。欲獲取這部分代碼,可以參考 GitHub 上的代碼倉庫。

          最后,我們設置解析器,在其中定義如何使用數(shù)據(jù)源。

          const?resolvers?=?{
          ??Query:?{
          ????joke:?async?(_source,?{?id?},?{?dataSources?})?=>
          ??????dataSources.jokesAPI.getJoke(id),
          ????jokes:?async?(_source,?_args,?{?dataSources?})?=>
          ??????dataSources.jokesAPI.getJokes(),
          ????rating:?async?(_source,?{?id?},?{?dataSources?})?=>
          ??????dataSources.ratingsAPI.getRating(id),
          ????ratings:?async?(_source,?_args,?{?dataSources?})?=>
          ??????dataSources.ratingsAPI.getRatings(),
          ??},
          ??Mutation:?{
          ????rating:?async?(_source,?{?jokeId,?score?},?{?dataSources?})?=>?{
          ??????const?rating?=?await?dataSources.ratingsAPI.postRating({?jokeId,?score?})
          ??????return?rating
          ????},
          ??},
          }

          module.exports?=?resolvers
          復制代碼

          完成這些步驟,我們一切準備就緒,可以通過 Apollo Server 調(diào)用 GraphQL API 了。為了把新的前端頁面和 GraphQL API 托管在 Heroku 上,我們需要創(chuàng)建并部署第二個應用程序:

          #?創(chuàng)建?Heroku?應用程序
          heroku?create?dad-joke-dadabase

          #?把代碼部署在?Heroku?上
          git?push?heroku?master

          #?在本地打開?Heroku?應用程序
          heroku?open
          復制代碼

          把 API 端點功能改為獲取笑話的代碼

          你應當回憶下,我們有兩個 API 端點供前端頁面調(diào)用:它們的功能分別是獲取笑話和提交評分。現(xiàn)在我們把 REST API 中獲取笑話的代碼改為 GraphQL API 形式:

          fetch('/jokes?_embed=ratings')
          ??.then(response?=>?response.json())
          ??.then(data?=>?{
          ????jokes.push(...data)
          ????displayNextJoke()
          ??})
          復制代碼

          我們把上述代碼改為:

          fetch('/graphql',?{
          ??method:?'POST',
          ??headers:?{?'Content-Type':?'application/json'?},
          ??body:?JSON.stringify({
          ????query:?`
          ????query?GetAllJokesWithRatings?{
          ??????jokes?{
          ????????id
          ????????content
          ????????ratings?{
          ??????????score
          ??????????id
          ??????????jokeId
          ????????}
          ??????}
          ????}
          ??`,
          ??}),
          })
          ??.then(res?=>?res.json())
          ??.then(res?=>?{
          ????jokes.push(...res.data.jokes)
          ????displayNextJoke()
          ??})
          復制代碼

          現(xiàn)在,我們可以在本地運行應用程序了。實際上,從用戶的角度來說,沒有發(fā)生任何變化。但假如你在瀏覽器的開發(fā)者工具中查看網(wǎng)絡請求,你會發(fā)現(xiàn),現(xiàn)在獲取笑話是通過訪問 /graphql 端點來實現(xiàn)的了。真棒!


          把 API 端點功能改為提交評分的代碼

          一個 API 請求已完成,還有一個!我們現(xiàn)在對評分功能的代碼進行修改。提交評分的代碼原來類似于:

          fetch('/ratings',?{
          ??method:?'POST',
          ??headers:?{
          ????'Content-Type':?'application/json',
          ??},
          ??body:?JSON.stringify(postData),
          })
          ??.then(response?=>?response.json())
          ??.then(responseData?=>?{
          ????const?jokeToUpdate?=?jokes.find(joke?=>?joke.id?===?responseData.jokeId)
          ????jokeToUpdate?&&?jokeToUpdate.ratings.push(responseData)
          ??})
          ??.finally(()?=>?{
          ????ratingInput.checked?=?false
          ????displayNextJoke()
          ??})
          復制代碼

          現(xiàn)在我們作如下的改動,讓它使用我們的 GraphQL API:

          fetch('/graphql',?{
          ??method:?'POST',
          ??headers:?{?'Content-Type':?'application/json'?},
          ??body:?JSON.stringify({
          ????query:?`
          ????mutation?CreateRating?{
          ??????rating(jokeId:?${jokeId},?score:?${score})?{
          ????????id
          ????????score
          ????????jokeId
          ??????}
          ????}
          ??`,
          ??}),
          })
          ??.then(res?=>?res.json())
          ??.then(res?=>?{
          ????const?rating?=?res.data.rating
          ????const?jokeToUpdate?=?jokes.find(joke?=>?joke.id?===?rating.jokeId)
          ????jokeToUpdate?&&?jokeToUpdate.ratings.push(rating)
          ??})
          ??.finally(()?=>?{
          ????ratingInput.checked?=?false
          ????displayNextJoke()
          ??})
          復制代碼

          經(jīng)過快速測試,這段代碼符合需求。再次說明,用戶體驗沒有變,但現(xiàn)在我們請求數(shù)據(jù)使用的都是 /graphql 端點。

          結論

          我們做到了。我們以已有的 REST API 為基礎,成功地實現(xiàn)了一個 GraphQL API 端點。因此,我們也能使用 GraphQL 來實現(xiàn)一些核心功能,而且已有的功能和原來的 REST API 都不需要修改。如今我們可以棄用 REST API,它將來也可能會退出歷史舞臺。

          雖然 dad joke 數(shù)據(jù)庫是完全虛擬的項目,但幾乎所有的在 2015 年 GraphQL 發(fā)布會之前成立的科技公司都發(fā)現(xiàn):如果他們改變技術路線,使用 GraphQL,他們自身的情況跟 dad jokes 一樣,也是可行的。另外,還有個好消息,Apollo Server 屬于較靈活的產(chǎn)品,它也可以從包括 REST API 端點在內(nèi)的各種數(shù)據(jù)源獲取數(shù)據(jù)。

          如果發(fā)現(xiàn)譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改并 PR,也可獲得相應獎勵積分。文章開頭的 本文永久鏈接 即為本文在 GitHub 上的 MarkDown 鏈接。


          最后





          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)

          2. 歡迎加我微信「huab119」拉你進技術群,長期交流學習...

          3. 關注公眾號「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。



          點個在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 57
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久av在线 | 人人人人人橾 | www夜夜撸 | 国产成人探花 | 亚洲成人操B视频 |