- Оригинальный адрес:How to Implement a GraphQL API on Top of an Existing REST API
- Оригинальный автор:Tyler Hawkins
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:samyu2000
- Корректор:PassionPenguin, k8scat
Где твои шутки про папу? Конечно вdadabaseвнутри. Давайте представим, что вы администратор самой популярной в мире базы данных анекдотов про пап. Технический обзор проекта: используйте REST API для связи с базой данных, этот REST API имеет возможность искать и оценивать шутки; посетители веб-сайта могут оценивать каждую шутку через простой пользовательский интерфейс.
Недавно вы узнали о новой технологии под названием GraphQL, которая обладает определенной гибкостью для получения именно тех данных, которые вам нужны, и использует один узел API. Звучит здорово, поэтому вы планируете использовать эту технику в своем приложении. Однако вы не хотите вносить слишком много изменений в исходный REST API. Может ли ваш проект поддерживать как REST API, так и GraphQL API?
В этой статье мы обсудим, как реализовать GraphQL API на основе существующего REST API. Используя этот подход, вы можете использовать GraphQL в незавершенных модулях вашего проекта без каких-либо корректировок существующей инфраструктуры на основе REST API.
Если вы хотите увидеть окончательный результат, вы можете посетитьREST API-кода такжеКод внешнего интерфейса и GraphQL API. также помнитеВзгляните на веб-сайт, эти шутки стоит посмотреть.
Начальная архитектура
Бэкэнд проекта изначально использовалNodeа такжеJSON Serverразвивающийся. Использование JSON-сервераExpressПолный REST API предоставляется для фиктивной базы данных, созданной из простого файла JSON. Внешний интерфейс реализован с использованием Vanilla JS и использует встроенный в браузерFetch APIСделайте запрос API. Приложение размещено наHeroku, его можно легко развернуть и контролировать.
Используемый нами файл 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 использует данные в этом файле в качестве исходных данных базы данных, а затем реализует набор REST API, включая поддержку запросов GET, POST, PUT, PATCH и DELETE. Магия JSON Server заключается в том, что файлы JSON можно изменять с помощью этого API, поэтому база данных полностью интерактивна. JSON Server можно запустить напрямую скриптом 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}`)
})
Чтобы протестировать эту фиктивную базу данных, вы можете поместитьРепозитории, связанные с APIКлонировать локально и запуститьnpm install
а такжеnpm start
. Доступ в браузереhttp://localhost:3000/jokes, на странице будут показаны все шутки. доступhttp://localhost:3000/ratings, на странице будет отображаться вся информация о рейтинге.
чудесный. Мы можем запустить фоном приложения на браузере. Теперь мы размещаем API на Heroku. Первая потребностьИнструмент установки командной строки Heroku. Затем мы можем выполнить следующие операции: войти в систему, создать проект, отправить на сервер Heroku и открыть интерфейс работы с проектом в браузере.
# 登录你的 Heroku 账户
heroku login
# 创建项目
heroku create dad-joke-dadabase-rest-api
# 将代码部署到 Heroku 服务端
git push heroku master
# 打开项目的后台页面
heroku open
Смотрите, теперь мы публикуем наш API в общедоступной сети!
Создайте пользовательский интерфейс
Теперь, когда у нас развернут работающий REST API, мы можем создать интерфейсную страницу и использовать API для отображения данных шутки на странице и оценки шуток. Следующий код HTML-страницы реализует контейнер для отображения содержимого шутки, который загружается кодом JavaScript.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dad Joke Dadabase</title>
<meta name="description" content="Where do you keep your dad jokes? In a dadabase of course!">
<meta name="author" content="Tyler Hawkins">
<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" name="yourRating" value="1" /><label for="score-1">1</label></span>
<span class="formGroup"></span><input type="radio" id="score-2" name="yourRating" value="2" /><label for="score-2">2</label></span>
<span class="formGroup"></span><input type="radio" id="score-3" name="yourRating" value="3" /><label for="score-3">3</label></span>
<span class="formGroup"></span><input type="radio" id="score-4" name="yourRating" value="4" /><label for="score-4">4</label></span>
<span class="formGroup"></span><input type="radio" id="score-5" name="yourRating" value="5" /><label for="score-5">5</label></span>
<span class="formGroup"></span><input type="radio" id="score-6" name="yourRating" value="6" /><label for="score-6">6</label></span>
<span class="formGroup"></span><input type="radio" id="score-7" name="yourRating" value="7" /><label for="score-7">7</label></span>
<span class="formGroup"></span><input type="radio" id="score-8" name="yourRating" value="8" /><label for="score-8">8</label></span>
<span class="formGroup"></span><input type="radio" id="score-9" name="yourRating" value="9" /><label for="score-9">9</label></span>
<span class="formGroup"></span><input type="radio" id="score-10" name="yourRating" value="10" /><label for="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>
Код JavaScript выглядит следующим образом. Ключевой код для взаимодействия с REST API — это два запроса на получение данных. К первому запросу обращается/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),
0
)
const numberOfRatings = joke.ratings.length
const averageRating = totalScore / numberOfRatings
jokeRatingValue.textContent = averageRating.toFixed(1)
}
const submitJokeRating = () => {
const ratingInput = document.querySelector('input[name="yourRating"]: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
Таким образом, мы завершили архитектуру проекта: у него есть простая страница, которая общается с базой данных через REST API. Итак, как мы используем GraphQL? Какая подготовка требуется перед использованием GraphQL? Первый шаг, устанавливаем[apollo-server-express](https://www.npmjs.com/package/apollo-server-express)
, который представляет собой пакет, реализующийApollo ServerИнтеграция с Экспресс. тоже надо установить[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:typeDefs
, resolvers
а такжеdataSources
. в,typeDefs
Атрибуты содержат информацию, связанную с нашим GraphQL API.schema, мы определяем типы данных шуток и оценок в соответствующих пакетах, а также способы запроса и обновления данных;resolvers
Сообщите серверу, как обрабатывать различные запросы и запросы на обновление, и как подключатьсяисточник данных;наконец,dataSources
Примерное описание того, как API-интерфейсы GraphQL связаны с API-интерфейсами REST.
Следующий код определяет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
Ниже приведен код класса JokesAPI, который в основном определяет методы создания, запроса, обновления и удаления данных шутки Эти методы соответственно вызывают соответствующий REST API для реализации связанных операций с данными.
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
Данные рейтинга аналогичны шутке, за исключением того, что «шутка» заменяется на «рейтинг» в каждом случае. Чтобы получить эту часть кода, вы можетеОбратитесь к репозиторию кода на GitHub..
Наконец, мы настраиваем парсер, в котором мы определяем, как использовать источник данных.
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
После выполнения этих шагов все готово для вызова GraphQL API через сервер Apollo. Чтобы разместить новую страницу внешнего интерфейса и GraphQL API на Heroku, нам нужно создать и развернуть второе приложение:
# 创建 Heroku 应用程序
heroku create dad-joke-dadabase
# 把代码部署在 Heroku 上
git push heroku master
# 在本地打开 Heroku 应用程序
heroku open
Измените функцию конечной точки API, чтобы получить код шутки
Вы должны помнить, что у нас есть две конечные точки API для вызова страницы интерфейса: их функции — получать шутки и отправлять рейтинги соответственно. Теперь давайте изменим код получения шуток из 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()
})
Теперь мы можем запустить приложение локально. По сути, с точки зрения пользователя ничего не изменилось. Но если вы посмотрите на сетевой запрос в инструментах разработчика вашего браузера, вы увидите, что теперь доступ к шутке осуществляется через посещение/graphql
Конечная точка реализована. Потрясающий!
Измените функцию конечной точки API, чтобы отправить код оценки
Один запрос 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()
})
Теперь мы вносим следующие изменения, чтобы использовать наш 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()
})
После быстрого теста этот код отвечает всем требованиям. Опять же, пользовательский опыт не изменился, но теперь мы запрашиваем данные, используя/graphql
конечная точка.
В заключение
Мы сделали это. Мы успешно внедрили конечную точку Graphql API на основе существующего REST API. Поэтому мы также можем использовать Graphql для реализации некоторых основных функций, существующие функции и исходные API REST не нужно изменять. Теперь мы можем отказаться от REST API, который также может выйти из исторической стадии в будущем.
Хотя база данных шуток папы — это полностью виртуальный проект, почти все технологические компании, которые были основаны до запуска GraphQL в 2015 году, обнаружили, что если бы они изменили свой технический маршрут и использовали GraphQL, их собственная ситуация, как шутки папы, стала бы осуществимой. Кроме того, хорошей новостью является то, что Apollo Server является более гибким продуктом, который также может извлекать данные из различных источников данных, включая конечные точки REST API.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.