Как реализовать GraphQL API на основе существующего REST API

задняя часть Программа перевода самородков GraphQL

Dad joke “dadabase” app

Где твои шутки про папу? Конечно в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, на странице будет отображаться вся информация о рейтинге.

/jokes API endpoint returns all the jokes when running the app locally

чудесный. Мы можем запустить фоном приложения на браузере. Теперь мы размещаем API на Heroku. Первая потребностьИнструмент установки командной строки Heroku. Затем мы можем выполнить следующие операции: войти в систему, создать проект, отправить на сервер Heroku и открыть интерфейс работы с проектом в браузере.

# 登录你的 Heroku 账户
heroku login

# 创建项目
heroku create dad-joke-dadabase-rest-api

# 将代码部署到 Heroku 服务端
git push heroku master

# 打开项目的后台页面
heroku open

Смотрите, теперь мы публикуем наш API в общедоступной сети!

/jokes API endpoint returns all the jokes when hosting the API on Heroku

Создайте пользовательский интерфейс

Теперь, когда у нас развернут работающий 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()
  })

Dad joke “dadabase” user interface allows you to rate each joke

Установите и используйте сервер 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Конечная точка реализована. Потрясающий!

The Network tab shows a request is being made to the /graphql endpoint now

Измените функцию конечной точки 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,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.