---
title: Nullability
tocDepth: 2
---

## Nullability in principle

When creating an API, especially before going to production or lifting features out of beta, thinking about if arguments and input object fields (_inputs_) should be required and if object type fields (_outputs_) should be nullable is an important design consideration. How easy your API is to consume trades for how easy it is to change and some reliability characteristics.

If inputs are optional or outputs are guaranteed then client developers will have a simpler API to deal with since making requests demands no up front configuration and handling responses presents no null cases. On the other hand, for the API developer, changing the API becomes harder since turning inputs from optional to required or making outputs go from guaranteed to nullable are breaking changes from the client's point of view.

Also, as more outputs are guaranteed, the greater the potential of the "null blast radius" can be. This is the effect where, within a schema runtime, a `null` or error received from some data source where the schema states there shall be no `null` requires propagating the `null` up the data tree until a nullable type is found (or, at root, finally error).

If you'd like to see these design considerations discussed further here are a few articles/resources you may find helpful:

- 2019 [Nullability in GraphQL](https://medium.com/expedia-group-tech/nullability-in-graphql-b8d06fbd8a3c) by Grant Norwood
- 2018 [Using nullability in GraphQL](https://blog.apollographql.com/using-nullability-in-graphql-2254f84c4ed7) by Sashko Stubailo
- 2017 [When To Use GraphQL Non-Null Fields](https://medium.com/@calebmer/when-to-use-graphql-non-null-fields-4059337f6fc8) by Caleb Meredith
- [Nullability Best Practices](https://graphql.org/learn/best-practices/#nullability) on graphql.org

## Nullability in Nexus

Nexus defaults to both inputs and outputs being nullable. This means by default your API is conservative in what it sends but flexible in what it accepts. With this approach, by default:

- You're free to defer some hard thinking about output nullability, knowing you can always change your mind later without breaking clients.
- Client developers work more to processing API responses, having to handle null conditions.
- You're forced to frontload some hard thinking about inputs, since realizing something should have been required later will require breaking clients.
- Client developers work less to satisfy minimum query requirements.
- The "null blast radius" (refer to [Nullability in Principal](#nullability-in-principle)) is reduced to zero.

There is no right or wrong answer to nullability. These are just defaults, not judgments. Understand the tradeoffs, and react to your use-case, above all.

You can override the global defaults at the per-type level or per-field level. If you find yourself writing local overrides in a majority of cases then it might mean the global defaults are a bad fit for your API. In that case you can change the global defaults.

When you make an input nullable then Nexus will alter its TypeScript type inside your resolver to have `null | undefined`. `null` is for the case that the client passed in an explicit `null` while `undefined` is for the case where the client simply did not specify the input at all.

If an arg has been given a default value, then it will be used when the client passes nothing, but since clients can still pass explicit `null`, resolvers must still handle nullability. If this surprises you then you may be interested in [#485](https://github.com/graphql-nexus/nexus/issues/485).

### Example: Default nullability settings

```ts
queryType({
  definition(t) {
    t.string('echo', {
      args: {
        message: 'String',
      },
      resolve(_root, args) {
        return args.message ?? 'nil'
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String): String
}
```

### Example: Nullability flipped at global level

```ts
makeSchema({
    nonNullDefaults: {
      input: false,
      output: false
    },
    types:[...]
})
```

```ts
queryType({
  definition(t) {
    t.string('echo', {
      args: {
        message: 'String',
      },
      resolve(_root, args) {
        return args.message
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String): String!
}
```

### Example: Nullability flipped at type level

```ts
queryType({
  nonNullDefaults: {
    input: true,
    output: true,
  },
  definition(t) {
    t.string('echo', {
      args: {
        message: 'String',
      },
      resolve(_root, args) {
        return args.message
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String): String!
}
```

### Example: Nullability flipped at input and field level

```ts
queryType({
  definition(t) {
    t.field('echo', {
      type: nonNull('String'),
      args: {
        message: nullable(stringArg()),
      },
      resolve(_root, args) {
        return args.message
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String): String!
}
```

### Example: Mixing levels

It is possible to use type and input/field layers together. This provides flexibility to optimize for local sections of your API that have different characteristics. For example here, a type deviates from the global default for all but but one field and its input.

```ts
queryType({
  // flip the global defaults
  nonNullDefaults: {
    input: true,
    output: true,
  },
  definition(t) {
    // ... Everything in this type uses the type-level
    // nullability config ... Except the following,
    // which effectively reverts back to what the global
    // defaults are:
    t.nonNull.string('echo', {
      args: {
        message: nonNull(stringArg()),
      },
      resolve(_root, args) {
        return args.message
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String!): String!
}
```

### Example: Args that have default values

When an arg has a default you might think that then it should be nullable to the client but non-nullable within your resolver logic. However it turns out that if the client passes an _explicit_ `null` then that is considered an actual value, and hence is not subject to being assigned the default value. Thus, and then, the resolver still can observe null from the client. If you are curious about seeing this change and/or become configurable then please refer to [#485](https://github.com/graphql-nexus/nexus/issues/485).

```ts
queryType({
  definition(t) {
    t.nonNull.string('echo', {
      args: {
        message: stringArg({
          default: 'nil via default',
        }),
      },
      resolve(_root, args) {
        const fallback = 'nil via client null'
        return args.message ?? fallback
      },
    })
  },
})
```

```graphql
type Query {
  echo(message: String = "nothing via default"): String!
}}
```

```graphql
query {
  echo1: echo
  echo2: echo(message: null)
}
```

```json
{
  "data": {
    "echo1": "nil via default",
    "echo2": "nil via client null"
  }
}
```
