Graph server error

Documentation for the Microsoft Graph REST API. Contribute to microsoftgraph/microsoft-graph-docs development by creating an account on GitHub.
title description ms.localizationpriority

Microsoft Graph error responses and resource types

Learn about errors that can be returned in Microsoft Graph responses. Errors are returned using standard HTTP status codes and a JSON error response object.

high

Microsoft Graph error responses and resource types

Errors in Microsoft Graph are returned using standard HTTP status codes, as well as a JSON error response object.

HTTP status codes

The following table lists and describes the HTTP status codes that can be returned.

Status code Status message Description
400 Bad Request Cannot process the request because it is malformed or incorrect.
401 Unauthorized Required authentication information is either missing or not valid for the resource.
403 Forbidden Access is denied to the requested resource. The user might not have enough permission.

Important: If conditional access policies are applied to a resource, a HTTP 403; Forbidden error=insufficent_claims may be returned. For more details on Microsoft Graph and conditional access see Developer Guidance for Azure Active Directory Conditional Access

404 Not Found The requested resource doesn’t exist.
405 Method Not Allowed The HTTP method in the request is not allowed on the resource.
406 Not Acceptable This service doesn’t support the format requested in the Accept header.
409 Conflict The current state conflicts with what the request expects. For example, the specified parent folder might not exist.
410 Gone The requested resource is no longer available at the server.
411 Length Required A Content-Length header is required on the request.
412 Precondition Failed A precondition provided in the request (such as an if-match header) does not match the resource’s current state.
413 Request Entity Too Large The request size exceeds the maximum limit.
415 Unsupported Media Type The content type of the request is a format that is not supported by the service.
416 Requested Range Not Satisfiable The specified byte range is invalid or unavailable.
422 Unprocessable Entity Cannot process the request because it is semantically incorrect.
423 Locked The resource that is being accessed is locked.
429 Too Many Requests Client application has been throttled and should not attempt to repeat the request until an amount of time has elapsed.
500 Internal Server Error There was an internal server error while processing the request.
501 Not Implemented The requested feature isn’t implemented.
503 Service Unavailable The service is temporarily unavailable for maintenance or is overloaded. You may repeat the request after a delay, the length of which may be specified in a Retry-After header.
504 Gateway Timeout The server, while acting as a proxy, did not receive a timely response from the upstream server it needed to access in attempting to complete the request. May occur together with 503.
507 Insufficient Storage The maximum storage quota has been reached.
509 Bandwidth Limit Exceeded Your app has been throttled for exceeding the maximum bandwidth cap. Your app can retry the request again after more time has elapsed.

The error response is a single JSON object that contains a single property
named error. This object includes all the details of the error. You can use the information returned here instead of or in addition to the HTTP status code. The following is an example of a full JSON error body.

{
  "error": {
    "code": "invalidRange",
    "message": "Uploaded fragment overlaps with existing data.",
    "innerError": {
      "request-id": "request-id",
      "date": "date-time"
    }
  }
}

Error resource type

The error resource is returned whenever an error occurs in the processing of a request.

Error responses follow the definition in the
OData v4
specification for error responses.

JSON representation

The error resource is composed of these resources:

{
  "error": { "@odata.type": "odata.error" }  
}

odata.error resource type

Inside the error response is an error resource that includes the following
properties:

{
  "code": "string",
  "message": "string",
  "innererror": { "@odata.type": "odata.error" }
}
Property name Value Description
code string An error code string for the error that occurred
message string A developer ready message about the error that occurred. This should not be displayed to the user directly.
innererror error object Optional. Additional error objects that may be more specific than the top level error.

Code property

The code property contains one of the following possible values. Your apps should be
prepared to handle any one of these errors.

Code Description
accessDenied The caller doesn’t have permission to perform the action.
activityLimitReached The app or user has been throttled.
extensionError The mailbox is located on premises and the Exchange server does not support federated Microsoft Graph requests, or an application policy prevents the application from accessing the mailbox.
generalException An unspecified error has occurred.
invalidRange The specified byte range is invalid or unavailable.
invalidRequest The request is malformed or incorrect.
itemNotFound The resource could not be found.
malwareDetected Malware was detected in the requested resource.
nameAlreadyExists The specified item name already exists.
notAllowed The action is not allowed by the system.
notSupported The request is not supported by the system.
resourceModified The resource being updated has changed since the caller last read it, usually an eTag mismatch.
resyncRequired The delta token is no longer valid, and the app must reset the sync state.
serviceNotAvailable The service is not available. Try the request again after a delay. There may be a Retry-After header.
syncStateNotFound The sync state generation is not found. The delta token is expired and data must be synchronized again.
quotaLimitReached The user has reached their quota limit.
unauthenticated The caller is not authenticated.

The innererror object might recursively contain more innererror objects
with additional, more specific error codes. When handling an error, apps
should loop through all the error codes available and use the most detailed
one that they understand. Some of the more detailed codes are listed at the
bottom of this page.

To verify that an error object is an error you are expecting, you must loop
over the innererror objects, looking for the error codes you expect. For example:

public bool IsError(string expectedErrorCode)
{
    OneDriveInnerError errorCode = this.Error;
    while (null != errorCode)
    {
        if (errorCode.Code == expectedErrorCode)
            return true;
        errorCode = errorCode.InnerError;
    }
    return false;
}

For an example that shows how to properly handle errors, see
Error Code Handling.

The message property at the root contains an error message intended for the
developer to read. Error messages are not localized and shouldn’t be displayed
directly to the user. When handling errors, your code should not branch based on
message values because they can change at any time, and they often contain
dynamic information specific to the failed request. You should only code
against error codes returned in code properties.

Detailed error codes

The following are some additional errors that your app might encounter within the nested
innererror objects. Apps are not required to handle these, but can if they
choose. The service might add new error codes or stop returning old ones at any
time, so it is important that all apps be able to handle the basic error codes.

Code Description
accessRestricted Access restricted to the item’s owner.
cannotSnapshotTree Failed to get a consistent delta snapshot. Try again later.
childItemCountExceeded Max limit on the number of child items was reached.
entityTagDoesNotMatch ETag does not match the current item’s value.
fragmentLengthMismatch Declared total size for this fragment is different from that of the upload session.
fragmentOutOfOrder Uploaded fragment is out of order.
fragmentOverlap Uploaded fragment overlaps with existing data.
invalidAcceptType Invalid accept type.
invalidParameterFormat Invalid parameter format.
invalidPath Name contains invalid characters.
invalidQueryOption Invalid query option.
invalidStartIndex Invalid start index.
lockMismatch Lock token does not match existing lock.
lockNotFoundOrAlreadyExpired There is currently no unexpired lock on the item.
lockOwnerMismatch Lock Owner ID does not match provided ID.
malformedEntityTag ETag header is malformed. ETags must be quoted strings.
maxDocumentCountExceeded Max limit on number of Documents is reached.
maxFileSizeExceeded Max file size exceeded.
maxFolderCountExceeded Max limit on number of Folders is reached.
maxFragmentLengthExceeded Max file size exceeded.
maxItemCountExceeded Max limit on number of Items is reached.
maxQueryLengthExceeded Max query length exceeded.
maxStreamSizeExceeded Maximum stream size exceeded.
parameterIsTooLong Parameter exceeds maximum length.
parameterIsTooSmall Parameter is smaller than minimum value.
pathIsTooLong Path exceeds maximum length.
pathTooDeep Folder hierarchy depth limit reached.
propertyNotUpdateable Property not updateable.
provisioningNotAllowed Request requires account provisioning, which is not allowed.
resourceBeingProvisioned Requested resource is being provisioned.
resyncApplyDifferences Resync required. Replace any local items with the server’s version (including deletes) if you’re sure that the service was up to date with your local changes when you last sync’d. Upload any local changes that the server doesn’t know about.
resyncRequired Resync is required.
resyncUploadDifferences Resync required. Upload any local items that the service did not return, and upload any files that differ from the server’s version (keeping both copies if you’re not sure which one is more up-to-date).
serviceNotAvailable The server is unable to process the current request.
serviceReadOnly Resource is temporarily read-only.
throttledRequest Too many requests.
tooManyResultsRequested Too many results requested.
tooManyTermsInQuery Too many terms in the query.
totalAffectedItemCountExceeded Operation is not allowed because the number of affected items exceeds threshold.
truncationNotAllowed Data truncation is not allowed.
uploadSessionFailed Upload session failed.
uploadSessionIncomplete Upload session incomplete.
uploadSessionNotFound Upload session not found.
virusSuspicious This document is suspicious and may have a virus.
zeroOrFewerResultsRequested Zero or fewer results requested.

conf-talks

В любом приложении возникают ошибки, и в вашим GraphQL API они тоже будут. Се ля ви.

Как работать с ошибками в GraphQL? К чему необходимо быть готовым клиентам вашего АПИ? Как лучше возвращать ошибки клиенту? Да и как вообще они возвращаются в GraphQL? В этот статье мы разберем как работать с ошибками в GraphQL.

Для начала давайте бегло посмотрим какие ошибки могут возникать и сразу разобьем их на группы:

  • ФАТАЛЬНЫЕ ОШИБКИ
    • 500 Internal Server Error
    • кончилась память
    • забыли установить пакет
    • грубая синтаксическая ошибка в коде
  • ОШИБКИ ВАЛИДАЦИИ
    • ошибка невалидного GraphQL-запроса
    • запросили несуществующее поле
    • не передали обязательный аргумент
    • не передали переменную
  • RUNTIME ОШИБКИ В RESOLVE-МЕТОДАХ
    • throw new Error(“”)
    • undefined is not a function (юзайте Flowtype или TypeScript уже в конце концов)
    • ошибка невалидного значения в return
  • ПОЛЬЗОВАТЕЛЬСКИЕ ОШИБКИ
    • запись не найдена
    • недостаточно прав для просмотра или редактирования записи

Как обычно GraphQL-сервер отвечает на ошибки?

Если произошла фатальная ошибка, то сервер возвращает 500 код. Это как обычно.

Но вот что необычное в GraphQL, так если произошла любая другая ошибка сервер возвращает код 200. Обычно бывалые REST API разработчики на этом моменте хотят выпрыгнуть из окна. Никаких вам 401, 403, 404 и прочих кодов не будет.

Сделали это так, потому что GraphQL по спецификации не привязан ни к какому протоколу. Вы можете гонять GraphQL-запросы через websockets, ssh, telnet ну и обычный http. Коль нет жесткой привязки к протоколу, то ошибки все унесли в тело ответа.

Вот так выглядит ответ от GraphQL по спецификации:

{
  data: {}, // для возврата данных
  errors: [...], // для возврата ошибок, массив между прочим 😳
  extensions: {}, // объект для пользовательских данных, сюда пихайте что хотите
  // другие ключи запрещены по спеке!
}

Первое что бросается в глаза так это то, что GraphQL возвращает массив ошибок. Wow! Т.к. запрос может быть сложный с запросом кучи ресурсов, то GraphQL может вернуть вам часть данных, а на оставшуюся часть вернуть ошибки. И это хорошо, пол ответа лучше, чем ничего.

Фатальные ошибки

Фатальная ошибка чаще всего имеет следующий вид — 500 Internal Server Error. Возникает обычно если кончилась память, забыли установить пакет, совершили грубую синтаксическую ошибку в коде. Да много еще чего. При этом дело не доходит до обработки GraphQL-запроса. И здесь резонно вернуть 500 ошибку.

Нет работы GraphQL, нет кода 200.

Фронтендеры обычно это дело должны обрабатывать на уровне своего Network Layer’a. Получили 500, значит где-то косячнулись бэкендеры с админами.

Ошибки валидации

Сервер получил запрос и делегировал его в пакет graphql. Перед тем как GraphQL-запрос будет выполняться он проходит парсинг и валидацию. Если кривой запрос, то никакие resolve-методы вызваны не будут и тупо будет возвращена ошибка:

{
  errors: [
    {
      message: 'Cannot query field "wrong" on type "Query".',
      locations: [{ line: 3, column: 11 }],
    },
  ],
}

// или например такая
{
  errors: [
    {
      message: 'Variable "$q" of required type "String!" was not provided.',
      locations: [{ line: 2, column: 16 }],
    },
  ],
}

При этом сервер вернет статус 200. При коде 200, ошибка обычно на стороне фронтендера. Но и бекендер может быть к этому причастен, если взял и удалил из схемы какое-то поле. В таком случае все старые работающие приложения теперь стали отправлять невалидные запросы.

Runtime ошибки в resolve-методах

Если запрос прошел парсинг и валидацию, то он начинает выполняться и вызывать resolve-методы вашей схемы согласно присланному GraphQL-запросу. И если вдруг внутри resolve-метода вываливается Exception (throw new Error()), неважно явно вы его выбросили, или он прилетел из недр чужих пакетов. То происходит следующая магия:

  • обработка ветки графа приостанавливается (вложенные resolve-методы вызываться не будут)
  • на месте элемента, где произошла ошибка возвращается null
  • ошибка добавляется в массив errors
  • НО при этом соседние ветки продолжают работать

Хорошо это понять можно на примере следующего кода:

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      search: {
        args: {
          q: { type: GraphQLString },
        },
        resolve: (_, args) => {
          if (!args.q) throw new Error('missing q');
          return { text: args.q };
        },
        type: new GraphQLObjectType({
          name: 'Record',
          fields: {
            text: {
              type: GraphQLString,
              resolve: source => source.text,
            },
          },
        }),
      },
    },
  }),
});

const res = await graphql({
  schema,
  source: `
    query {
      s1: search(q: "ok") { text }
      s2: search { text }
      s3: search(q: "good") { text }
    }
  `,
});

Ответ от сервера будет получен следующий:

{
  errors: [
    { message: 'missing q', locations: [{ line: 4, column: 11 }], path: ['s2'] }
  ],
  data: { s1: { text: 'ok' }, s2: null, s3: { text: 'good' } },
}

Поле s1 возвращает полный результат. В s2 была выброшена ошибка, поэтому оно стало null и в массив errors добавилась ошибка. И дальше поле s3 тоже без проблем вернулось.

Т.е. получается на тех местах, где была выброшена ошибка возвращается null и пишется ошибка в массив. А вся остальная часть запроса продолжает выполняться как ни в чем не бывало. Вот такой вот он добрый GraphQL, хоть что-нибудь да вернет.

Точно также работает, если бэкендер вернул данные неправильного типа в resolve-методе. GraphQL не позволяет вернуть “левые данные” в data.

Вот пример, когда мы по схеме должны вернуть массив строк, но второй элемент не является строкой. Вместо “левого” значения, он вернет null и при этом добавит ошибку в массив:

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      ooops: {
        type: new GraphQLList(GraphQLString),
        resolve: () => ['ok', { hey: 'wrong non String value' }],
      },
    },
  }),
});

const res = await graphql(schema, `query { ooops }`);

expect(res).toEqual({
  errors: [
    {
      message: 'String cannot represent value: { hey: "wrong non String value" }',
      locations: [{ line: 3, column: 11 }],
      path: ['ooops', 1],
    },
  ],
  data: { ooops: ['ok', null] },
});

Также спецификация GraphQL позволяет передать дополнительные данные вместе с ошибкой через проперти extensions. Давайте создадим объект ошибки и присвоим ему два проперти extensions и someOtherData:

new GraphQLObjectType({
  name: 'Query',
  fields: {
    search: {
      resolve: () => {
        const e: any = new Error('Some error');
        e.extensions = { a: 1, b: 2 }; // will be passed in GraphQL-response
        e.someOtherData = { c: 3, d: 4 }; // will be omitted
        throw e;
      },
      type: GraphQLString,
    },
  },
});

На выходе в GraphQL-ответе мы получим следующие данные (extensions будет передан, а все другие проперти из объекта ошибки будут опущены, например не будет someOtherData из нашего примера):

{
  errors: [
    {
      message: 'Some error',
      locations: [{ line: 1, column: 9 }],
      path: ['search'],
      extensions: { a: 1, b: 2 },
    },
  ],
  data: { search: null },
}

Такой механизм позволяет передать клиентам дополнительные данные об ошибке.

Ну коль заговорили про фронтенд, давайте пофантазируем как им работать с такими ошибками. На верхнем уровне одну ошибку в модальном окне вывести не проблема, а если ошибок две? А если у нас сложное приложение и ошибки надо показывать в разных частях приложения? Вот тут у фронтендера начинается просто адская боль и печаль с таким массивом ошибок. Его надо отдельно парсить, понимать какая именно ошибка произошла (например через extensions.code). Как-то передать ошибку в нужную компоненту и на нужный уровень. В общем, приходится сильно изгаляться в коде пробросом лишних проперти и логикой.

Если вам интересно как бэкендер может упростить жизнь фронтендеру, то обязательно читайте следующий раздел.

Пользовательские ошибки

Что такое пользовательские ошибки? Ну это когда вам где-то в приложении надо вывести “запись не найдена”, или “у вас нет прав просматривать этот контент”, или “необходимо подтвердить возраст” или в списке на 23 элементе показать что “запись удалена”.

Если пользоваться стандартным механизмом ошибок GraphQL. То на фронтенде приходится сильно изгаляться, чтобы пробросить ошибку в нужное место.

Но эту проблему можно достаточно элегантно решить, если ошибки возвращать прямо в data на нужном уровне, а не через глобальный массив errors. Для этого в GraphQL есть Union-типы, которые возвращают либо запись с данными, либо ошибку.

Давайте сразу к живому примеру. Представим что нам надо вернуть список видео. Причем какие-то видео в обработке, другие перед просмотром необходимо купить или подтвердить свой возраст. Так давайте и будем возвращать список, который может вернуть Union-тип из Video, VideoInProgressProblem, VideoNeedBuyProblem и VideoApproveAgeProblem. Со стороны фронтендера можно тогда написать вот такой запрос:

query {
  list {
    __typename # <----- магическое поле, которое вернет имя типа для каждой записи
    ...on Video {
      title
      url
    }
    ...on VideoInProgressProblem {
      estimatedTime
    }
    ...on VideoNeedBuyProblem {
      price
    }
    ...on VideoApproveAgeProblem {
      minAge
    }
  }
}

Т.е. используем фрагменты на конкретных типах и запрашиваем поле __typename, которое возвращает имя типа. К запросу выше GraphQL-ответ будет следующий:

{
  data: {
    list: [
      { __typename: 'Video', title: 'DOM2 in the HELL', url: 'https://url' },
      { __typename: 'VideoApproveAgeProblem', minAge: 21 },
      { __typename: 'VideoNeedBuyProblem', price: 10 },
      { __typename: 'VideoInProgressProblem', estimatedTime: 220 },
    ],
  },
}

При таком подходе фронтендер знает какие вообще ошибки могут быть. Также он получает ошибки в нужной компоненте, на нужном уровне. Код захламляется только там, где необходимо разобрать разные варианты пользовательских ошибок и вывести либо данные, либо красивый блок с ошибочкой.

Причем фронтендеры могут легко понять, какой тип ошибки вернулся. И при этом получить дополнительные данные по ошибке, если она их возвращает. Это же просто обычный тип в схеме, который может содержать в себе любые необходимые поля.

Для себя я вынес одно правило, что пользовательским ошибкам лучше всего давать суффикс Problem, а не Error. Это позволяет избежать путаницы как на бэкенде, так и на фронтенде.

Как это дело можно организовать на бэкенде? Достаточно просто. Вот пример:

// Объявляем класс Видео
class Video {
  title: string;
  url: string;

  constructor({ title, url }) {
    this.title = title;
    this.url = url;
  }
}

// И сразу же объявим GraphQL-тип
const VideoType = new GraphQLObjectType({
  name: 'Video',
  fields: () => ({
    title: { type: GraphQLString },
    url: { type: GraphQLString },
  }),
});


// Объявим классы проблем (ошибок)
class VideoInProgressProblem {
  constructor({ estimatedTime }) {
    this.estimatedTime = estimatedTime;
  }
}
class VideoNeedBuyProblem {
  constructor({ price }) {
    this.price = price;
  }
}
class VideoApproveAgeProblem {
  constructor({ minAge }) {
    this.minAge = minAge;
  }
}

// И их типы для GraphQL
const VideoInProgressProblemType = new GraphQLObjectType({
  name: 'VideoInProgressProblem',
  fields: () => ({
    estimatedTime: { type: GraphQLInt },
  }),
});
const VideoNeedBuyProblemType = new GraphQLObjectType({
  name: 'VideoNeedBuyProblem',
  fields: () => ({
    price: { type: GraphQLInt },
  }),
});
const VideoApproveAgeProblemType = new GraphQLObjectType({
  name: 'VideoApproveAgeProblem',
  fields: () => ({
    minAge: { type: GraphQLInt },
  }),
});

// Ну а теперь самое интересное.
// Объявляем наш UNION-тип который будет возвращать либо видео, либо проблему-ошибку
const VideoResultType = new GraphQLUnionType({
  // Даем имя типу.
  // Здорово если если вы выработаете конвенцию в своей команде
  // и к таким Union-типам будете добавлять суффикс Result
  name: 'VideoResult',

  // как хорошие бекендеры добавляем какое-нибудь описание
  description: 'Video or problems',

  // объявляем типы через массив, которые могут быть возвращены
  types: () => [
    VideoType,
    VideoInProgressProblemType,
    VideoNeedBuyProblemType,
    VideoApproveAgeProblemType,
  ],

  // Ну и самое главное надо объявить функцию определения типа.
  // resolve-функции (смотри ниже поле Query.list) просто возвращают JS-объект
  // но вот GraphQL'ю нужно как-то JS-объект, сконвертировать в GraphQL-тип
  // иначе как он узнает что надо записать в поле __typename
  resolveType: value => {
    if (value instanceof Video) {
      return VideoType;
    } else if (value instanceof VideoInProgressProblem) {
      return VideoInProgressProblemType;
    } else if (value instanceof VideoNeedBuyProblem) {
      return VideoNeedBuyProblemType;
    } else if (value instanceof VideoApproveAgeProblem) {
      return VideoApproveAgeProblemType;
    }
    return null;
  },
});

// Ну и вишенка на торте
// Пишем простую схемку, которая нам возвращает массив из Видео и Ошибок-Проблем.
const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      list: {
        type: new GraphQLList(VideoResultType),
        resolve: () => {
          return [
            new Video({ title: 'DOM2 in the HELL', url: 'https://url' }),
            new VideoApproveAgeProblem({ minAge: 21 }),
            new VideoNeedBuyProblem({ price: 10 }),
            new VideoInProgressProblem({ estimatedTime: 220 }),
          ];
        },
      },
    },
  }),
});

Очень просто и красиво. А самое главное удобно для фронтендеров:

  • знают какие ошибки могут быть
  • знают какие поля содержатся в ошибках
  • отлично поддерживается статический анализ, в отличии от обычных ошибок
  • ошибки возвращаются в дереве ответа, а не в глобальном массиве
  • в результате чище, проще и безопаснее код

Любите брата фронтендера своего 😉 Иначе они придут с вилами!

Ссылки по теме

  • Примеры кода в виде тестов к этой статье
  • Видео про ошибки от Sasha Solomon
  • Похожее видео про ошибки от Eloy Durán, всё-таки у Саши лучше

Whenever Apollo Server encounters errors while processing a GraphQL operation, its response to the client includes an errors array containing each error that occurred. Each error in the array has an extensions field that provides additional useful information, including an error code and (while in development mode) a stacktrace.

Here’s an example error response caused by misspelling the __typename field in a query:

To help with debugging, Apollo Server provides an ApolloServerErrorCode enum, which you can use to check if your error is one of the different types produced by Apollo Server.

You can check an error’s code to determine why an error occurred and also add logic to respond to different types of errors, like so:

import { ApolloServerErrorCode } from '@apollo/server/errors';

if (error.extensions?.code === ApolloServerErrorCode.GRAPHQL_PARSE_FAILED) {

} else if (error.extensions?.code === "MY_CUSTOM_CODE") {

Apollo Server’s variety of error codes enables requesting clients to respond differently to different error types. You can also create your own custom errors and codes.

Built-in error codes

Code Description
GRAPHQL_PARSE_FAILED

The GraphQL operation string contains a syntax error.

GRAPHQL_VALIDATION_FAILED

The GraphQL operation is not valid against the server’s schema.

BAD_USER_INPUT

The GraphQL operation includes an invalid value for a field argument.

PERSISTED_QUERY_NOT_FOUND

A client sent the hash of a query string to execute via automatic persisted queries, but the query was not in the APQ cache.

PERSISTED_QUERY_NOT_SUPPORTED

A client sent the hash of a query string to execute via automatic persisted queries, but the server has disabled APQ.

OPERATION_RESOLUTION_FAILURE

The request was parsed successfully and is valid against the server’s schema, but the server couldn’t resolve which operation to run.

This occurs when a request containing multiple named operations doesn’t specify which operation to run (i.e.,operationName), or if the named operation isn’t included in the request.

BAD_REQUEST

An error occurred before your server could attempt to parse the given GraphQL operation.

INTERNAL_SERVER_ERROR

An unspecified error occurred.

When Apollo Server formats an error in a response, it sets the code extension to this value if no other code is set.

Custom errors

You can create a custom errors and codes using the graphql package’s GraphQLError class, like so:

import { GraphQLError } from 'graphql';

throw new GraphQLError(message, {

extensions: { code: 'YOUR_ERROR_CODE', myCustomExtensions },

Custom errors can provide additional context, enabling your clients to understand why an error is happening. We recommend making clear errors for common cases, for example, when a user isn’t logged in (UNAUTHENTICATED), or someone is forbidden from performing an action:

import { GraphQLError } from 'graphql';

throw new GraphQLError('You are not authorized to perform this action.', {

Throwing errors

Apollo Server throws errors automatically when applicable. For example, it throws a GRAPHQL_VALIDATION_FAILED error whenever an incoming operation isn’t valid against the server’s schema.

Your resolvers can also throw errors in situations where Apollo Server doesn’t do so automatically.

For example, this resolver throws a custom error if the integer value provided for a user’s ID is less than 1:

If a resolver throws a generic error that is not a GraphQLError instance, that error is still thrown with an extensions field that includes a stacktrace and code (specifically INTERNAL_SERVER_ERROR), along with any other relevant error details.

Including custom error details

Whenever you throw a GraphQLError, you can add arbitrary fields to the error’s extensions object to provide additional context to the client. You specify these fields in an object you provide to the error’s constructor.

This example builds on the one above by adding the name of the GraphQL argument that was invalid:

This results in a response like the following:

Omitting or including stacktrace

The stacktrace error field is useful while developing and debugging your server, but you probably don’t want to expose it to clients in production.

By default, Apollo Server omits the stacktrace field if the NODE_ENV environment variable is set to either production or test.

You can override this default behavior by passing the includeStacktraceInErrorResponses option to the constructor of ApolloServer. If includeStacktraceInErrorResponses is true, stacktrace is always included. If it’s false, stacktrace is always omitted.

Note that when stacktrace is omitted, it’s also unavailable to your application. To log error stacktraces without including them in responses to clients, see Masking and logging errors.

Masking and logging errors

You can edit Apollo Server error details before they’re passed to a client or reported to Apollo Studio. This enables you to omit sensitive or irrelevant data.

For client responses

In the examples below, we use top-level await calls to start our server asynchronously. Check out our Getting Started guide to see how we configured our project to support this.

The ApolloServer constructor accepts a formatError hook that is run on each error before it’s passed back to the client. You can use this function to log or mask particular errors.

The formatError hook receives two arguments: the first is the error formatted as a JSON object (to be sent with the response), and the second is the original error.

The formatError function does not modify errors that are sent to Apollo Studio as part of usage reporting. See For Apollo Studio reporting.

The below example returns a user-friendly message whenever Apollo Server throws a GRAPHQL_VALIDATION_FAILED error:

import { ApolloServer } from '@apollo/server';

import { startStandaloneServer } from '@apollo/server/standalone';

import { ApolloServerErrorCode } from '@apollo/server/errors';

const server = new ApolloServer({

formatError: (formattedError, error) => {

formattedError.extensions.code ===

ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED

message: "Your query doesn't match the schema. Try double-checking it!",

const { url } = await startStandaloneServer(server);

console.log(`🚀 Server listening at: ${url}`);

As another example, here we return a more generic error whenever the original error’s message begins with Database Error: :

formatError: (formattedError, error) => {

if (formattedError.message.startsWith('Database Error: ')) {

return { message: 'Internal server error' };

If you want to access the originally thrown error (without the JSON formatting), you can use formatError‘s second argument.

For example, if you are using a database package in your app and you’d like to do something when your server throws a specific type of database error:

formatError: (formattedError, error) => {

if (error instanceof CustomDBError) {

Note, if a resolver throws the error, a GraphQLError is wrapped around the initially thrown error. This GraphQLError neatly formats the error and contains useful fields, such as the path where the error occurred.

If you want to remove the outer GraphQLError to access the originally thrown error you can use unwrapResolverError from @apollo/server/errors. The unwrapResolverError function can remove the GraphQLError wrapping from a resolver error or return the error unaltered if it isn’t from a resolver.

So, we can rewrite the above code snippet to work for errors thrown in and outside of resolvers, like so:

import { unwrapResolverError } from '@apollo/server/errors';

formatError: (formattedError, error) => {

if (unwrapResolverError(error) instanceof CustomDBError) {

return { message: 'Internal server error' };

To make context-specific adjustments to the error received by formatError (such as localization or personalization), consider creating a plugin that uses the didEncounterErrors lifecycle event to attach additional properties to the error. These properties can be accessed from formatError.

For Apollo Studio reporting

New in Apollo Server 4: error details are not included in traces by default. Instead, <masked> replaces each error’s message, and the maskedBy error extension replaces all other extensions. The maskedBy extension includes the name of the plugin that performed the masking (ApolloServerPluginUsageReporting or ApolloServerPluginInlineTrace).

You can use Apollo Studio to analyze your server’s error rates. By default, the operations sent to Studio as detailed traces don’t contain error details.

If you do want error information sent to Studio, you can send every error, or you can modify or redact specific errors before they’re transmitted.

To send all errors to Studio you can pass { unmodified: true } to sendErrors, like so:

ApolloServerPluginUsageReporting({

sendErrors: { unmodified: true },

If you want to report specific errors or modify an error before reporting it, you can pass a function to the sendErrors.transform option, like so:

ApolloServerPluginUsageReporting({

if (err.extensions.code === 'MY_CUSTOM_CODE') {

The usage reporting plugin is installed automatically with its default configuration if you provide an Apollo API key to Apollo Server. To customize the usage reporting plugin’s behavior, you need to install it explicitly with a custom configuration, as shown in the examples below.

The function you pass to transform is called for each error (GraphQLError) to be reported to Studio. The error is provided as the function’s first argument. The function can either:

  • Return a modified form of the error (e.g., by changing the err.message to remove potentially sensitive information)
  • Return null to prevent the error from being reported entirely

Note that returning null also affects Studio’s aggregated statistics about how many operations contain errors and at what paths those errors appear.

As mentioned above, you can use the unwrapResolverError (from @apollo/server/errors) to remove the GraphQLError wrapping an original error.

For federated graphs, define your transform function in each subgraph’s inline trace plugin to rewrite field errors. If you want to transform your gateway’s parsing or validation errors, you can define your transform function in your gateway.

Example: Ignoring common low-severity errors

Let’s say our server is throwing an UNAUTHENTICATED error whenever a user enters an incorrect password. We can avoid reporting these errors to Apollo Studio by defining a transform function, like so:

import { ApolloServer } from '@apollo/server';

import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';

const server = new ApolloServer({

ApolloServerPluginUsageReporting({

if (err.extensions.code === 'UNAUTHENTICATED') {

This example configuration ensures that any UNAUTHENTICATED error that’s thrown within a resolver is only reported to the client, and never sent to Apollo Studio. All other errors are transmitted to Studio normally.

Example: Filtering errors based on other properties

When generating an error (e.g., new GraphQLError("Failure!")), the error’s message is the most common extension (in this case it’s Failure!). However, any number of extensions can be attached to the error (such as a code extension).

We can check these extensions when determining whether an error should be reported to Apollo Studio using the transform function as follows:

import { ApolloServer } from '@apollo/server';

import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';

const server = new ApolloServer({

ApolloServerPluginUsageReporting({

if (err.message && err.message.startsWith('Known error message')) {

This example configuration ensures that any error that starts with Known error message is not transmitted to Apollo Studio, but all other errors are sent as normal.

Example: Redacting information from an error message

As mentioned above, by default, the operations sent to Studio as detailed traces don’t contain error details.

If you do want to send an error’s details to Apollo Studio, but need to redact some information first, the transform function can help.

For example, if there is personally identifiable information in the error message, like an API key:

import { GraphQLError } from 'graphql';

"The x-api-key:12345 doesn't have sufficient privileges.",

The transform function can ensure that such information is not sent to Apollo Studio and potentially revealed outside its intended scope:

import { ApolloServer } from '@apollo/server';

import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';

const server = new ApolloServer({

ApolloServerPluginUsageReporting({

err.message = err.message.replace(/x-api-key:[A-Z0-9-]+/, 'REDACTED');

In this case, the error above is reported to Apollo Studio as:

The REDACTED doesn't have sufficient privileges.

Setting HTTP status code and headers

GraphQL, by design, does not use the same conventions from REST to communicate via HTTP verbs and status codes. Client information should be contained in the schema or as part of the standard response errors field. We recommend using the included Error Codes or Custom Errors for error consistency rather than directly modifying the HTTP response.

Apollo Server uses different HTTP status codes in various situations:

  • If Apollo Server hasn’t correctly started up or is in the process of shutting down, it responds with a 500 status code.
  • The former can happen if you use a serverless integration and it sends requests to an Apollo Server instance that had an error on startup. The latter happens if you aren’t properly draining your server.
  • If Apollo Server can’t parse the request into a legal GraphQL document and validate it against your schema, it responds with a 400 status code. This can also happen with other request problems, such as if a client attempts to send a batched HTTP request when allowBatchedHttpRequests isn’t enabled or if CSRF prevention blocks a request.
  • If a request uses an invalid HTTP method (GET with a mutation, or any HTTP method other than GET or POST), then Apollo Server responds with a 405 status code.
  • If your context function throws, Apollo Server responds with a 500 status code.
  • If there is an unexpected error during the processing of the request (either a bug in Apollo Server or a plugin hook throws), Apollo Server responds with a 500 status code.
  • Otherwise, Apollo Server returns a 200 status code. This is essentially the case where the server can execute the GraphQL operation, and execution completes successfully (though this can still include resolver-specific errors).

There are three ways to change an HTTP status code or set custom response headers, you can: throw an error in a resolver, throw an error in your context function, or write a plugin.

While Apollo Server does enable you to set HTTP status codes based on errors thrown by resolvers, best practices for GraphQL over HTTP encourage sending 200 whenever an operation executes. So, we don’t recommend using this mechanism in resolvers, just in the context function or in a plugin hooking into an early stage of the request pipeline.

Be aware that GraphQL client libraries might not treat all response status codes the same, so it will be up to your team to decide which patterns to use.

To change the HTTP status code and response headers based on an error thrown in either a resolver or context function, throw a GraphQLError with an http extension, like so:

import { GraphQLError } from 'graphql';

throw new GraphQLError('the error message', {

code: 'SOMETHING_BAD_HAPPENED',

['some-header', 'it was bad'],

['another-header', 'seriously'],

import { GraphQLError } from 'graphql';

throw new GraphQLError('the error message', {

code: 'SOMETHING_BAD_HAPPENED',

['some-header', 'it was bad'],

['another-header', 'seriously'],

You don’t need to include status unless you want to override the default status code (200 for a resolver or 500 for a context function). The optional headers field should provide a Map with lowercase header names.

If your setup includes multiple resolvers which throw errors that set status codes or set the same header, Apollo Server might resolve this conflict in an arbitrary way (which could change in future versions). Instead, we recommend writing a plugin (as shown below).

You can also set the HTTP status code and headers from a plugin. As an example, here is how you could set a custom response header and status code based on a GraphQL error:

async requestDidStart() {

async willSendResponse({ response }) {

response.http.headers.set('custom-header', 'hello');

if (response.body.kind === 'single' &&

response.body.singleResult.errors?.[0]?.extensions?.code === 'TEAPOT') {

response.http.status = 418;

const server = new ApolloServer({

plugins: [setHttpPlugin],

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Granite 2000000 error
  • Grand theft space an error has occurred
  • Grand theft auto vc cannot find enough available video memory как исправить
  • Grand theft auto 3 requires at least 12mb of available video memory как исправить
  • Grand rhapsody piano encountered an error while loading samples как исправить

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии