如何基于已有的 REST API 实现 GraphQL API
阅读本文大概需要 28 分钟。
初始架构
{
"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 }
]
}
复制代码
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}`)
})
复制代码
npm install
和 npm start
。在浏览器中访问 http://localhost:3000/jokes ,页面会显示所有的笑话。访问 http://localhost:3000/ratings ,页面会显示所有的评分信息。# 登录你的 Heroku 账户
heroku login
# 创建项目
heroku create dad-joke-dadabase-rest-api
# 将代码部署到 Heroku 服务端
git push heroku master
# 打开项目的后台页面
heroku open
复制代码
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta >
<title>Dad Joke Dadabase</title>
<meta Where do you keep your dad jokes? In a dadabase of course!">
<meta >
<link rel="stylesheet" href="./style.css">
</head>
<body>
<h1>Dad Joke Dadabase</h1>
<div class="project">
<h2 class="jokeContent"></h2>
<div class="rateThisJokeContainer">
<p>Rate this joke:</p>
<div class="rateThisJokeOptions">
<span class="formGroup"><input type="radio" id="score-1" >1</label></span>
<span class="formGroup"></span><input type="radio" id="score-2" >2</label></span>
<span class="formGroup"></span><input type="radio" id="score-3" >3</label></span>
<span class="formGroup"></span><input type="radio" id="score-4" >4</label></span>
<span class="formGroup"></span><input type="radio" id="score-5" >5</label></span>
<span class="formGroup"></span><input type="radio" id="score-6" >6</label></span>
<span class="formGroup"></span><input type="radio" id="score-7" >7</label></span>
<span class="formGroup"></span><input type="radio" id="score-8" >8</label></span>
<span class="formGroup"></span><input type="radio" id="score-9" >9</label></span>
<span class="formGroup"></span><input type="radio" id="score-10" >10</label></span>
</div>
</div>
<p class="averageRating">Average Rating: <span class="jokeRatingValue">7.8</span></p>
<button id="nextJoke">See Next Joke</button>
</div>
<script src="./script.js"></script>
</body>
</html>
复制代码
/jokes?_embed=ratings
获取数据库中所有的笑话,第二个请求是 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
[apollo-server-express](https://www.npmjs.com/package/apollo-server-express)
,它是一个程序包,用于实现 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}`)
})
复制代码
typeDefs
, resolvers
和 dataSources
。其中,typeDefs
属性包含了与我们的 GraphQL API 相关的 schema,我们在相应的包中定义笑话和评分的数据类型,以及如何查询和更新数据;resolvers
告诉服务器如何处理各种各样的查询和更新需求,以及如何连接数据源;最后,dataSources
大致描述了 GraphQL API 与 REST API 的关联关系。Joke
和 Rating
数据类型,以及如何查询和更新数据。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
复制代码
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
复制代码
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
复制代码
# 创建 Heroku 应用程序
heroku create dad-joke-dadabase
# 把代码部署在 Heroku 上
git push heroku master
# 在本地打开 Heroku 应用程序
heroku open
复制代码
把 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()
})
复制代码
/graphql
端点来实现的了。真棒!把 API 端点功能改为提交评分的代码
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()
})
复制代码
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()
})
复制代码
/graphql
端点。结论
End
「进击的Coder」专属学习群已正式成立,搜索「CQCcqc4」添加崔庆才的个人微信或者扫描下方二维码拉您入群交流学习。
点个在看你最好看
原文始发于微信公众号(进击的Coder):如何基于已有的 REST API 实现 GraphQL API
THE END
0
二维码
海报
如何基于已有的 REST API 实现 GraphQL API
这是「进击的Coder」的第 356 篇技术分享
作者:Tyler Hawkins
译者:samyu2000
校对者:PassionPenguin, k8scat
来源:掘金开发者社区
……