Skip to content

Latest commit

 

History

History
363 lines (318 loc) · 8.03 KB

GraphQL-REST.md

File metadata and controls

363 lines (318 loc) · 8.03 KB

REST

TODO

GraphQL

GraphQLязык запросов (query language) для API.

Клиент посылает запрос (query) к сервису GraphQL и получает ответ в виде JSON-объекта по указанной в запросе схеме.

Например, клиент может послать запрос.

query {
  currentUser {
    id
    username
    role
  }
}

Если сервер позволяет получить данные по заданной выше схеме, по клиенту придёт ответ, соответствующий этой схеме.

{
  "data": {
    "currentUser": {
      "id": "1",
      "username": "Notes",
      "role": "Admin"
    }
  }
}

Запросы query и mutation

Существуют два типа запросов (requests): query и mutation.

Запрос query отвечает за получение данных, он не может их изменять (аналог GET-запроса в REST).

query {
  users {
    id
    username
  }
}

Запрос mutation отвечает за изменение данных (объединяет в себе возможности POST, PUT, DELETE и других в REST).

mutation {
  logOut
}

Схемы и типы

Все поля, которые может получить клиент, должны быть описаны в типе (type).

Типы Query и Mutation являются типами по умолчанию. В них должны быть описаны все возможные запросы.

type Query {
  currentUser: User
}

type Mutation {
  logOut
}
schema {
  query: Query
  mutation: Mutation
}

Могут создаваться пользовательские типы.

type User {
  id: ID
  username: String
  role: String
  image: String
}

Если схема в запросе с клиента не удовлетворяет схеме на сервере, то возникнет валидационная ошибка. Например, поле age отсутствует в типе User.

{
  "data": null,
  "errors": [
    {
      "...": "...",
      "message": "Validation error of type FieldUndefined: Field 'age' in type 'User' is undefined
    }
  ]
}

Динамические объекты в GraphQL

GraphQL не позволяет создавать динамические объекты в качестве type.

Примеры динамических объектов.

const commentsMap = {
  123: {
    id: "123",
    message: "Hello"
  },
  456: {
    id: "456",
    message: "Hi"
  }
};
const reportsInfoMap = {
  spam: ["id1", "id2"],
  flood: [],
  bullying: ["id3"]
}
const flags = {
  isReviewed: true,
  isVisited: true,
  /* ... */
}

Есть два решения, как это можно обойти

  • Передавать объекты текстовый как JSON и указывать встроенный тип String (не лучшая валидация) или подключить библиотеку, которая имеет скалярный тип JSON (например, эту). Например, AWS AppSync имеет встроенный тип AWSJSON.
type Res {
  commentsMap: String
  reportsMap: JSON
  flags: AWSJSON
}
  • Представить объекты в виде массивов (предпочтительный вариант).
type Comment {
  id: ID
  message: String
}

type ReportsInfo {
  name: String
  values: [ID]
}

type Flag {
  name: String
  value Boolean
}

type Res {
  commentsMap: [Comment]
  reportsMap: [ReportsInfo]
  flags: [Flag]
}

Взаимодействие с сервером

Отправка query

query Users {
  users {
    id
    username
    role
  }
}

Query с переменными

query User($userId: ID!) {
  user(userId: $userId) {
    id
    username
    role
  }
}

Variables

{
  "userId": "auth0|72d45e398924235638341891"
}

Query с input

query QueryLeaders($credentialsInput: Credentials) {
  login(credentials: $credentialsInput) {
    auth_token
  }
}

Variables

{
  "credentialsInput": {
    "username": "admin",
    "password": "admin"
  }
}

Преимущества GraphQL

  • Строгая типизация. Конкретная схема, полностью описывающая, как можно работать с данными.
type User {
  id: ID!
  username: String!
}

type Article {
  id: ID!
  title: String!
  description: String
  author: User!
}

type Query {
  articles: [Article]
  article(id: ID!): Article
}
  • Клиент всегда запрашивает только то, что ему нужно. Он не может получить те поля, которые не запрашивал, ровно как и те, которые не предусмотрены описанной схемой.

Следующий запрос вернёт articles, содержащие только поля id и title. Все эти поля должны присутствовать в схеме запроса на бэкенде (описана выше), иначе будет ошибка.

query {
  articles {
    id
    title
  }
}
  • Один запрос может объединять в себе несколько различных ресурсов.

Следующий запрос объединяет в себе информацию, собранную из Articles и Users.

query {
  articles {
    id
    title
    description
    author {
      id
      username
    }
  }
}

REST на практике

Данные в QUERY

  • Форма запроса на клиенте.
GET /route?field=value&anotherField=anotherValue
/* axios */
axios.get('/route', {
  params: {
    field: 'value',
    anotherField: 'anotherValue',
  },
});
  • Форма запроса на сервере.
GET /route
  • Получение данных из запроса на сервере.
/* Express */
app.get('/route', (request, response) => {
  const { field, anotherField } = request.query;
});

Передача массива arr [1, 3, 7] с клиента.

GET /route?arr[]=1&arr[]=3&arr[]=7

Передача объекта obj { foo: '1', bar: '7' } с клиента.

GET /route?obj[foo]=1&obj[bar]=7

Данные в BODY

  • Форма запроса на клиенте.
POST /route
Content-Type: application/json

{ "field": "value", "anotherField": "anotherValue" }
/* axios */
axios.post('/route', {
  field: 'value',
  anotherField: 'anotherValue',
});
  • Форма запроса на сервере.
POST /route
  • Получение данных из запроса на сервере.
/* Express */
app.post('/route', (request, response) => {
  const { field, anotherField } = request.body;
});

Данные в PARAMS

  • Форма запроса на клиенте.
GET /route/paramValue
/* axios */
axios.get(`/route/${paramValue}`);
  • Форма запроса на сервере.
GET /route/:param
  • Получение данных из запроса на сервере.
/* Express */
app.post('/route/:param', (request, response) => {
  const { param } = request.params;
});

Пример отправки нескольких параметров.

PATCH /users/17/name
PATCH /users/:id/:field