Skip to content

Latest commit

 

History

History
1201 lines (877 loc) · 28.8 KB

schema.md

File metadata and controls

1201 lines (877 loc) · 28.8 KB

import { schema }

issues / features | bugs

Use the schema to model your domain, all the data that your API will accept and return, and how all the various objects in the domain relate to one another (the "graph" in "GraphQL").

objectType

GraphQL Docs for Object Types

The most common kind of type in most GraphQL schemas. of a GraphQL schema are object types, a type you can fetch from your schema, with fields:

Signature
objectType(config: {
  name:             string
  description?:     string
  rootTyping?:      NexusGenBackingTypes
  nonNullDefaults?: NonNullConfig
  definition:       ObjectDefinitionBlock
}) => NexusObjectType
  • name (required) The name of this object.

  • definition (required) The function used to define the fields of this object. See below for the various field builders available.

  • description The description of this object. Tools like GraphQL Playground can display this content.

  • nonNullDefaults NonNullConfig

  • rootTyping NexusGenBackingTypes

Example
import { schema } from 'nexus'

schema.objectType({
  name: 'User',
  definition(t) {
    t.int('id', { description: 'Id of the user' })
    t.string('fullName', { description: 'Full name of the user' })
    t.list.field('posts', {
      type: 'Post',
      resolve(post, args, ctx) {
        return ctx.db.user.getOne(post.id).posts()
      },
    })
  },
})

schema.objectType({
  name: 'Post',
  definition(t) {
    t.int('id')
    t.string('title')
  },
})

t.field

Signature
(
  name: string,
  config: FieldConfig
) => void

todo

t.<scalar>

t.id
t.string
t.boolean
t.int
t.float

Field builder specialization for GraphQL scalar types: string, boolean, int, float, id. They are like t.field but omit the config.type property and accept a resolver as their second parameter as an alternative to full-on field config.

Signature
(name: string, param?: UntypedFieldConfig | Resolver) => void

t.list

Use this to express a list of some other type. All field builders are available as properties.

t.implements

todo

Signature
(...interfaceNames: string[]) => void

t.modify

Modify a field added via an interface.

Signature
(fieldName: string, modifications: todo) => void

todo

t.connection

This field builder helps you implement paginated associations between types in your schema. The contributions that it makes to your GraphQL schema adhere to the Relay Connection Specification. In other words it allows you the API author to write the minimum logic required to create spec-compliant relay connections for your API clients.

Signature
(
  fieldName: string,
  config: {
    type:                       GraphQLType,
    additionalArgs?:            Args
    inheritAdditionalArgs?:     boolean
    disableForwardPagination?:  boolean
    disableBackwardPagination?: boolean
    strictArgs?:                boolean
    validateArgs?:              (
                                  argsArgs: Args,
                                  info: ResolverInfo
                                ) => void
    extendConnection?:          (t: TypeBuilder) => void
    extendEdge?:                (t: TypeBuilder) => void
    pageInfoFromNodes?:         (
                                  nodes:    Node[],
                                  args:     Args,
                                  context:  Context,
                                  info:     ResolverInfo
                                ) => {
                                       hasNextPage:     boolean,
                                       hasPreviousPage: boolean
                                     }
    cursorFromNode?:            (
                                  node:    Node,
                                  args:    Args,
                                  context: Context,
                                  info:    ResolverInfo,
                                  forCursor: {
                                    index: number
                                    nodes: Node[]
                                  }
                                ) => MaybePromise<string>
  } & 
    | { nodes?:   never, resolve: Resolver } 
    | { resolve?: never, nodes: NodeResolver } 
) => void
  • param config

    • type GraphQLType
      The type of this field.

    • resolve Resolver
      Implement everything yourself.

      Useful for more complex pagination cases, where you may want to use utilities from other libraries like graphql-relay, and only use Nexus for the construction and type-safety.

      Unlike with nodes approach, this approach makes no assumptions about values for the edges cursor pageInfo properties.

      Optionality

      Forbidden if nodes given. Required otherwise.

    • nodes NodeResolver

      Optionality

      Forbidden if resolve given. Required otherwise.

      Remarks

      When you use this approach (instead of resolve), Nexus makes some assumptions about the structure of the connection. You are only required to return a list of nodes to resolve based on the connection, and then we will automatically infer the hasNextPage, hasPreviousPage, and cursor values for you.

      The returned array of nodes should have a length of one greater than the requested item count. The additional item should be placed according to this pattern:

      • paginating forward / selecting first: last
      • paginating backward / selecting last: first

      For example, if the query is paginating forward, and there are 20 nodes in the returned array:

      Query Args     Returned Nodes
      
      (first: 2)     [{id: 1}, {id: 2}, {id: 3}]
                                      ~~~~~~~ ------------------- Extra
      (last: 2)      [{id: 18}, {id: 19}, {id: 20}]
                     ~~~~~~~~ ------------------------------------- Extra
      

      Nexus then slices the array in the paginating direction, and if there are more than "N" node results, Nexus takes this to mean that there is another page in the paginating direction.

      If you set assumeExactNodeCount to true in schema.connections setting then this heuristic changes. Nexus then assumes that a next page exists if the returned array length is >= to requested node count.

    • additionalArgs Args
      Additional arguments to use for just this field.

      Default

      undefined

      Remarks

      When used, the additionalArgs in app settings schema.connections will not be inherited. If you do wish to inherit them, enable that with inheritAdditionalArgs.

    • inheritAdditionalArgs
      Whether to inherit the additionalArgs from app settings schema.connections

      Default

      true if additionalArgs is not set, false otherwise.

    • disableForwardPagination
      If true then first and after args are not present. When disabled, last arg becomes required, unless you disable strictArgs.

      Default

      false

    • disableBackwardPagination
      If true then last and before args are not present. When disabled, first arg becomes required, unless you disable strictArgs.

      Default

      false

    • strictArgs
      Whether first/last arg nullability should reflect the forward/backward pagination configuration. When true, then the following pattern is used:

      • when only forward pagination enabled
        • meaning, disableForwardPagination && !disableBackwardPagination
        • then, last arg is required
      • when only backward pagination enabled
        • meaning, !disableForwardPagination && disableBackwardPagination
        • then, first arg is required
      Default

      true

    • validateArgs
      Custom logic to validate the args. Throw an error to signal validation failure.

      Signature

      (args: Args, info: ResolverInfo) => void
      Default

      Validates that client passes a first or a last arg, and not both.

    • extendConnection Dynamically add additional fields to the GraphQL connection object. Similar to extendEdge.

      Signature

      (t: TypeBuilder) => void
      Default

      undefined

      Remarks

      Because this customizes the GraphQL connection object type, the name of the type will necessarily be changed as well. If it didn't, it would conflict with the non-extended connection type in your schema (if any). The following pattern will be used to name the GraphQL object type:

      {camelCaseJoin: <typeName><fieldName>}_Connection
      
      Example
      schema.queryType({
        name: 'Query',
        definition(t) {
          t.connection("toto", {
            type: 'Boolean',
            extendConnection(t) {
              t.string('foo', () => 'bar')
            }
          }),
          ...
        }
      })
      type QueryToto_Connection {
        edges: [BooleanEdge]
        pageInfo: PageInfo!
        foo: String!
      }
      ...
    • extendEdge
      Dynamically add additional fields to the GraphQL edge object. Similar to extendConnection.

      Signature

      (t: TypeBuilder) => void
      Default

      undefined

      Remarks

      Because this customizes the GraphQL edge object type, the name of the type will necessarily be changed as well. If it didn't, it would conflict with the non-extended edge type in your schema (if any). The following pattern will be used to name the GraphQL object type:

      {camelCaseJoin: <typeName><fieldName>}_Edge
      
      Example
      schema.queryType({
        name: 'Query',
        definition(t) {
          t.connection("toto", {
            type: 'Boolean',
            extendEdge(t) {
              t.string('foo', () => 'bar')
            }
          }),
          ...
        }
      })
      type QueryToto_Edge {
        cursor: String!
        node: Boolean!
        foo: String!
      }
      ...
    • pageInfoFromNodes
      Override the default algorithm to determine hasNextPage and hasPreviousPage page info fields. Often needed when using cursorFromNode. See nodes for what default algorithm is.

      Signature
      (
        nodes:   Node[],
        args:    Args,
        context: Context,
        info:    ResolverInfo
      ) => {
        hasNextPage:     boolean,
        hasPreviousPage: boolean
      }
      Default

      undefined

    • cursorFromNode Approach we use to transform a node into a cursor

      Signature
      (
        node:    Node,
        args:    Args,
        context: Context,
        info:    ResolverInfo,
        forCursor: {
          index: number
          nodes: Node[]
        }
      ) => MaybePromise<string>
      Default

      'nodeField'

SchemaContributions {docsify-ignore}

todo

Example of using resolve {docsify-ignore}
import { schema } from 'nexus'
import { connectionFromArray } from 'graphql-relay'

schema.queryType({
  definition(t) {
    t.connection('users', {
      type: 'User',
      async resolve(root, args, ctx, info) {
        return connectionFromArray(await ctx.resolveUserNodes(), args)
      },
    })
  },
})
Example of using nodes {docsify-ignore}
schema.queryType({
  definition(t) {
    t.connection('users', {
      type: 'User',
      nodes(root, args, ctx, info) {
        // [{ id: 1,  ... }, ..., { id: 10, ... }]
        return ctx.users.resolveForConnection(root, args, ctx, info)
      },
    })
  },
})

One limitation of the nodes property, is that you cannot paginate backward without a cursor, or without defining a cursorFromNode property on either the field or plugin config. This is because we can't know how long the connection list may be to begin paginating backward.

schema.queryType({
  definition(t) {
    t.connection('usersConnectionNodes', {
      type: 'User',
      cursorFromNode(node, args, ctx, info, { index, nodes }) {
        if (args.last && !args.before) {
          const totalCount = USERS_DATA.length
          return `cursor:${totalCount - args.last! + index + 1}`
        }
        return connectionPlugin.defaultCursorFromNode(node, args, ctx, info, {
          index,
          nodes,
        })
      },
      nodes() {
        // ...
      },
    })
  },
})
Example of using additionalArgs {docsify-ignore}
schema.queryType({
  definition(t) {
    t.connection('userConnectionAdditionalArgs', {
      type: 'User',
      disableBackwardPagination: true,
      additionalArgs: {
        isEven: schema.booleanArg({
          description: 'If true, filters the users with an odd pk',
        }),
      },
      resolve() {
        // ...
      },
    })
  },
})
Example of extending connection type globally {docsify-ignore}
settings.change({
  schema: {
    connections: {
      extendConnection: {
        totalCount: {
          type: 'Int',
        },
      },
    },
  },
})

schema.queryType({
  definition(t) {
    t.connection('users', {
      type: 'User',
      nodes() {
        // ...
      },
      totalCount() {
        return ctx.users.totalCount(args)
      },
    })
  },
})
Example of extending connection type for one field {docsify-ignore}
schema.queryType({
  definition(t) {
    t.connection('users', {
      extendConnection(t) {
        t.int('totalCount', {
          resolve(source, args, ctx) {
            return ctx.users.totalCount(args),
          }
        })
      },
    })
  },
})

queryType

Refer to objectType. This is a shorthand where config.name is assigned Query.

mutationType

Refer to objectType. This is a shorthand where config.name is assigned Mutation.

subscriptionType

Not implemented, please see #447.

inputObjectType

GraphQL Docs for Input Object Types

Defines an object which can be passed as an input value.

Signature
(config: {
  description?:      string
  nonNullDefaults?:  NonNullConfig
  definition:        InputObjectDefinitionBlock
}) => NexusInputObjectType
  • name
    The name of this object.

  • description
    The description of this object. Tools like GraphQL Playground can display this content.

    Default

    undefined

  • nonNullDefaults
    todo

    Default

    undefined

  • definition
    See below for the various field builders available.

Example
import { schema } from 'nexus'

schema.inputObjectType({
  name: 'MyInput',
  definition(t) {
    t.string('foo', { required: true })
    t.int('bar')
  },
})

schema.objectType({
  name: 'Qux',
  definition(t) {
    t.string('toto', {
      args: {
        myInput: 'MyInput',
      },
    })
  },
})
input MyInput {
  bar: Int
  foo: String!
}

type Qux {
  toto(myInput: MyInput): String
}

Unlike object types, input types do not have arguments, so they do not have resolvers or "backing types"

t.field

todo

t.<scalar>

todo

t.list

todo

enumType

GraphQL Docs for Enum Types

Signature
enumType(config: NexusEnumTypeConfig): NexusEnumTypeDef
NexusEnumTypeConfig options
  • name (required): Name of your type

  • members (required): All members of the enum, either as an array of strings/definition objects, as an object, or as a TypeScript enum

  • description (optional): The description to annotate the GraphQL SDL

  • rootTyping (optional): Root type information for this type. By default, types are extracted for any .ts file in your project. You can configure that from the schema.rootTypingsGlobPattern setting

Example

Defining as an array of enum values:

import { schema } from 'nexus'

const Episode = schema.enumType({
  name: 'Episode',
  members: ['NEWHOPE', 'EMPIRE', 'JEDI'],
  description: 'The first Star Wars episodes released',
})

As an object, with a simple mapping of enum values to internal values:

import { schema } from 'nexus'

const Episode = schema.enumType({
  name: 'Episode',
  members: {
    NEWHOPE: 4,
    EMPIRE: 5,
    JEDI: 6,
  },
})

interfaceType

GraphQL Docs for Interface Types

In Nexus, you do not need to redefine the interface fields on the implementing object types, instead you may use .implements(interfaceName) and all of the interface fields will be added to the type.

Signature
interfaceType(config: NexusInterfaceTypeConfig): NexusInterfaceTypeDef
NexusInterfaceTypeConfig options
  • name (required): Name of your type

  • definition (required): A function to define the fields of your type

  • description (optional): The description to annotate the GraphQL SDL

  • nonNullDefaults (optional): Configures the nullability for the type, check the documentation's "Getting Started" section to learn more about GraphQL Nexus's assumptions and configuration on nullability.

  • rootTyping (optional): Root type information for this type. By default, types are extracted for any .ts file in your project. You can configure that from the schema.rootTypingsGlobPattern setting

Example
import { schema } from 'nexus'

schema.interfaceType({
  name: 'Node',
  definition(t) {
    t.id('id', { description: 'GUID for a resource' })
  },
})

schema.objectType({
  name: 'User',
  definition(t) {
    t.implements('Node')
  },
})

If you need to modify the description or resolver defined by an interface, you can call the modify method on objectType to change these after the fact.

scalarType

GraphQL Docs for Scalar Types

Nexus allows you to provide an asNexusMethod property which will make the scalar available as a builtin on the definition block object. We automatically generate and merge the types so you get type-safety just like the scalar types specified in the spec.

Signature
scalarType(config: NexusScalarTypeConfig): NexusScalarTypeDef
NexusScalarTypeConfig options
  • name (required): Name of your type

  • serialize (required): Serializes an internal value to include in a response

  • description (optional): The description to annotate the GraphQL SDL

  • deprecation (optional): Any deprecation info for this scalar type

  • parseValue (optional): Parses an externally provided value to use as an input

  • parseLiteral (optional): Parses an externally provided literal value to use as an input

  • asNexusMethod (optional): Adds this type as a method on the Object/Interface definition blocks

  • rootTyping (optional): Root type information for this type. By default, types are extracted for any .ts file in your project. You can configure that from the schema.rootTypingsGlobPattern setting

Example
import { schema } from 'nexus'

schema.scalarType({
  name: 'Date',
  asNexusMethod: 'date',
  description: 'Date custom scalar type',
  parseValue(value) {
    return new Date(value)
  },
  serialize(value) {
    return value.getTime()
  },
  parseLiteral(ast) {
    if (ast.kind === Kind.INT) {
      return new Date(ast.value)
    }
    return null
  },
})

unionType

GraphQL Docs for Union Types

Union types are very similar to interfaces, but they don't get to specify any common fields between the types.

Signature
unionType(config: NexusUnionTypeConfig): NexusUnionTypeDef
NexusUnionTypeConfig options
  • name (required): Name of your type

  • description (optional): The description to annotate the GraphQL SDL

  • deprecation (optional): Info about a field deprecation. Formatted as a string and provided with the deprecated directive on field/enum types and as a comment on input field

  • rootTyping (optional): Root type information for this type. By default, types are extracted for any .ts file in your project. You can configure that from the schema.rootTypingsGlobPattern setting

Example
import { schema } from 'nexus'

schema.unionType({
  name: 'MediaType',
  description: 'Any container type that can be rendered into the feed',
  definition(t) {
    t.members('Post', 'Image', 'Card')
    t.resolveType((item) => item.name)
  },
})

arg

GraphQL Docs on Arguments

Signature
(config: {
  type:         'Boolean' | 'Float' | 'Int' | 'String' | 'ID'
  default?:     TYPEGEN
  description?: string
  list?:        null | true | boolean[]
  nullable?:    boolean
  required?:    boolean
}) => NexusArgDef
  • type
    The type of the argument.

  • required
    Whether the argument is required or not.

    When true then nullable is false.

  • nullable
    Whether the argument is nullable or not.

    When true then required is false.

  • list
    Whether the argument is a list or not.

    When true it is a list.

    When an array, the inner boolean specifies whether the list member can be nullable.

  • description
    The description to annotate the GraphQL SDL

Example

Defines an argument that can be used in any object or interface type. Args can be reused in multiple locations, and it can be convenient to create your own wrappers around arguments.

import { schema } from 'nexus'
import { ScalarArgConfig } from 'nexus/types'

function requiredInt(opts: ScalarArgConfig<number>) {
  return schema.arg({ ...opts, required: true, type: 'Int' })
}

<scalar>Arg

idArg
stringArg
booleanArg
intArg
floatArg

Sugar for creating arguments of type Int String Float ID Boolean.

addToContext

Add context to your graphql resolver functions. The objects returned by your context contributor callbacks will be shallow-merged into ctx. The ctx type will also accurately reflect the types you return from callbacks passed to addToContext.

Example
import { schema } from 'nexus'

schema.addToContext((_req) => {
  return {
    greeting: 'Howdy!',
  }
})

schema.queryType({
  definition(t) {
    t.string('hello', {
      resolve(_root, _args, ctx) {
        return ctx.greeting
      },
    })
  },
})

use

Add schema plugins to your app. These plugins represent a subset of what framework plugins (app.use) can do. This is useful when, for example, a schema plugin you would like to use has not integrated into any framework plugin. You can find a list of schema plugins here.

Example
import { schema } from 'nexus'
import somePlugin from 'some-plugin'

schema.use(somePlugin())

schema.use({
  name: 'myPlugin',
  description: 'my inline schema plugin',
  // ...
})

middleware

Run arbitrary logic before and/or after GraphQL resolvers in your schema. Middleware is run as a first-in-first-out (FIFO) stack. The order of your middleware definitions determines their order in the stack.

Signature
schema.middleware((config: CreateFieldResolverInfo) => MiddlewareFn | undefined)): void

schema.middleware expect a function with the signature (root, args, ctx, info, next) to be returned.

If the current middleware function does not end the request-response cycle, it must call next to pass control to the next middleware function, until the actual GraphQL resolver gets called.

Note: You can skip the creation of a middleware by returning undefined instead of a middleware.

Example: Simple middlewares
// graphql.ts
import { schema } from 'nexus'

schema.middleware((_config) => {
  return async (root, args, ctx, info, next) => {
    ctx.log.info('before - middleware 1')
    const result = await next(root, args, ctx, info)
    ctx.log.info('after - middleware 1')
    return result
  }
})

schema.middleware((_config) => {
  return async (root, args, ctx, info, next) => {
    ctx.log.info('before - middleware 2')
    const result = await next(root, args, ctx, info)
    ctx.log.info('after - middleware 2')
    return result
  }
})

schema.queryType({
  definition(t) {
    t.string('hello', (_root, _args, ctx) => {
      ctx.log.info('executing resolver')
      return Promise.resolve('world')
    })
  },
})

/**
 * Output
 * before - middleware 1
 * before - middleware 2
 * executing resolver
 * after - middleware 2
 * after - middleware 1
 */
Example: Trace resolvers completion time of the Query type only
import { schema } from 'nexus'

schema.middleware((config) => {
  if (config.parentTypeConfig.name !== 'Query') {
    return
  }

  return async (root, args, ctx, info, next) => {
    const startTimeMs = new Date().valueOf()
    const value = await next(root, args, ctx, info)
    const endTimeMs = new Date().valueOf()
    const resolver = `Query.${config.fieldConfig.name}`
    const completionTime = endTimeMs - startTimeMs

    ctx.log.info(`Resolver '${resolver}' took ${completionTime} ms`, {
      resolver,
      completionTime,
    })

    return value
  }
})

schema.queryType({
  definition(t) {
    t.string('hello', async () => {
      // Wait two seconds
      await new Promise((res) => setTimeout(res, 2000))
      return 'world'
    })
  },
})

/**
 * Output:
 * ● server:request Resolver 'Query.hello' took 2001 ms  --  resolver: 'Query.hello'  time: 2001
 */

importType

Signature
(scalarType: GraphQLScalarType, methodName?: string): GraphQLScalarType
(type: GraphQLNamedType): GraphQLNamedType

schema.importType is useful for adding existing GraphQL.js types into your Nexus schema.

Check out this repository for a handful list of useful scalar types that might be useful to your GraphQL API.

When passing a GraphQLScalarType, you can additionally pass a methodName as a second parameter, which will augment the t parameter of your definition builder with a convenient method to create a field of the associated type.

Example: Adding a date scalar type
import { schema } from 'nexus'
import { GraphQLDate } from 'graphql-iso-date'

schema.importType(GraphQLDate, 'date')

schema.objectType({
  name: 'SomeObject',
  definition(t) {
    t.date('createdAt') // t.date() is now available (with types!) thanks to `importType`
  },
})

schema.importType can also be used to add types from an existing GraphQL schema into your Nexus schema. This is useful to incrementally adopt Nexus if you already have a GraphQL schema built with a different technology than Nexus.

Example: Adding types from a schema-first schema
import { schema } from 'nexus'
import { existingSchema } from './existing-schema'

Object.values(existingSchema.getTypeMap()).forEach(schema.importType)

Type Glossary

I FieldConfig

{
  type:         string
  args?:        Args
  description?: string
  deprecation?: string
  nullable?:    boolean
  list?:        true | boolean[]
  resolve:      Resolver
}

O Args

todo

U GraphQLType

todo

I TypeBuilder

todo

I Context

todo

I ResolverInfo

todo

I Node

todo

I NonNullConfig

todo

{
  /**
   * Whether output fields are non-null by default.
   *
   * type Example {
   *   field: String!
   *   otherField: [String!]!
   * }
   *
   * @default true
   */
  output?: boolean;
  /**
   * Whether input fields (field arguments, input type members)
   * are non-null by default.
   *
   * input Example {
   *   field: String
   *   something: [String]
   * }
   *
   * @default false
   */
  input?: boolean;
}

I RootTypingImport

todo

{
  /**
   * File path to import the type from.
   */
  path: string;
  /**
   * Name of the type we want to reference in the `path`
   */
  name: string;
  /**
   * Name we want the imported type to be referenced as
   */
  alias?: string;
}

F Resolver

todo

S NexusGenBackingTypes

todo

By default, types are extracted for any .ts file in your project. You can configure that from the schema.rootTypingsGlobPattern setting