Skip to content
NestJS ns graphql 5 min read

Object Types, Args & Inputs

In the code-first approach, your GraphQL schema is generated from TypeScript classes decorated with metadata. Object types describe what your API returns, input types and args types describe what it accepts, and field decorators tune nullability, lists, and scalar mapping. Getting these decorators right is the difference between a clean, self-documenting SDL and a pile of String columns that lie about your data. This page walks through every building block you need to model a NestJS GraphQL schema precisely.

Object types and fields

An object type is a class annotated with @ObjectType(). Each property you expose becomes a GraphQL field via @Field(). NestJS infers the GraphQL type from TypeScript metadata for primitives, but because TypeScript erases types like number (which could map to Int or Float), you should pass an explicit type function for anything non-obvious.

import { ObjectType, Field, Int, ID, Float } from '@nestjs/graphql';

@ObjectType()
export class Author {
  @Field(() => ID)
  id: string;

  @Field()
  name: string;

  @Field({ nullable: true, description: 'Optional public bio' })
  bio?: string;

  @Field(() => Int)
  followerCount: number;

  @Field(() => Float)
  rating: number;
}

The () => Type thunk avoids circular-reference problems and is mandatory for Int, Float, ID, lists, and any reference to another decorated class. A bare @Field() is only safe for string and boolean, where the runtime metadata is unambiguous.

This produces the following SDL:

Output:

type Author {
  id: ID!
  name: String!
  bio: String
  followerCount: Int!
  rating: Float!
}

Nullable and list fields

By default every field is non-nullable (! in SDL). Use the nullable option to relax that. For lists, the type function returns an array, and nullable accepts 'items', 'itemsAndList', or a boolean to control nullability at different levels.

import { ObjectType, Field } from '@nestjs/graphql';

@ObjectType()
export class Post {
  @Field()
  title: string;

  // [String!]!  — non-null list of non-null strings
  @Field(() => [String])
  tags: string[];

  // [String]    — nullable list, nullable items
  @Field(() => [String], { nullable: 'itemsAndList' })
  optionalTags?: (string | null)[];
}
nullable valueSDL resultMeaning
false (default)[String!]!List required, items required
true[String!]List optional, items required
'items'[String]!List required, items optional
'itemsAndList'[String]Both optional

Input types for mutations

GraphQL forbids passing object types as arguments, so write data flows through @InputType() classes. They look like object types but are meant for the request side. Pair them with class-validator decorators and a ValidationPipe to validate at the edge.

import { InputType, Field, Int } from '@nestjs/graphql';
import { IsEmail, Length, Min } from 'class-validator';

@InputType()
export class CreateAuthorInput {
  @Field()
  @Length(2, 60)
  name: string;

  @Field()
  @IsEmail()
  email: string;

  @Field(() => Int, { nullable: true })
  @Min(0)
  followerCount?: number;
}
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { Author } from './author.model';
import { CreateAuthorInput } from './create-author.input';
import { AuthorService } from './author.service';

@Resolver(() => Author)
export class AuthorResolver {
  constructor(private readonly authors: AuthorService) {}

  @Mutation(() => Author)
  createAuthor(@Args('input') input: CreateAuthorInput): Promise<Author> {
    return this.authors.create(input);
  }
}

Tip: Register app.useGlobalPipes(new ValidationPipe()) in main.ts so class-validator rules on input types run automatically. NestJS surfaces failures as BAD_USER_INPUT GraphQL errors.

Args types

When a query or mutation takes several scalar arguments, you can list them individually with @Args('name'), or group them into a reusable @ArgsType() class. Unlike @InputType, an args type is spread as flat arguments in the schema rather than nested under a single input object.

import { ArgsType, Field, Int } from '@nestjs/graphql';
import { Min, Max } from 'class-validator';

@ArgsType()
export class PaginationArgs {
  @Field(() => Int, { defaultValue: 0 })
  @Min(0)
  offset: number;

  @Field(() => Int, { defaultValue: 20 })
  @Min(1)
  @Max(100)
  limit: number;
}
@Query(() => [Author])
authors(@Args() args: PaginationArgs): Promise<Author[]> {
  return this.authorService.findAll(args.offset, args.limit);
}

Note that @Args() is called with no name here — NestJS flattens the args class into separate schema arguments:

Output:

type Query {
  authors(offset: Int! = 0, limit: Int! = 20): [Author!]!
}
DecoratorSchema shapeUse when
@InputTypeSingle nested input objectMutations with a cohesive payload
@ArgsTypeMultiple flat argumentsQueries with a few independent params

Custom scalars

Out of the box GraphQL only knows Int, Float, String, Boolean, and ID. For dates and arbitrary JSON, use the graphql-scalars package, which ships battle-tested scalar implementations.

npm install graphql-scalars

Register them as named scalars in your GraphQLModule, then reference them from @Field:

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { GraphQLJSON, GraphQLDateTime } from 'graphql-scalars';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'schema.gql',
      resolvers: { JSON: GraphQLJSON, DateTime: GraphQLDateTime },
    }),
  ],
})
export class AppModule {}
import { ObjectType, Field } from '@nestjs/graphql';
import { GraphQLJSON, GraphQLDateTime } from 'graphql-scalars';

@ObjectType()
export class AuditLog {
  @Field(() => GraphQLDateTime)
  createdAt: Date;

  @Field(() => GraphQLJSON, { nullable: true })
  metadata?: Record<string, unknown>;
}

This serializes Date instances to ISO-8601 strings and parses them back automatically, while JSON lets you accept or return free-form structured data without declaring its shape.

Warning: Avoid overusing the JSON scalar. It is a typed escape hatch — the moment you reach for it, you lose schema validation and client codegen for that field. Prefer a concrete object type whenever the shape is known.

Best Practices

  • Always pass an explicit () => Type thunk for Int, Float, ID, lists, and references to other types; rely on inference only for plain string and boolean.
  • Keep @ObjectType classes for outputs and @InputType/@ArgsType for inputs — never reuse an output model as an argument.
  • Add description to fields and types; it flows straight into the SDL and powers GraphQL Playground docs.
  • Co-locate class-validator decorators on input types and enable a global ValidationPipe so bad payloads fail fast as BAD_USER_INPUT.
  • Choose @ArgsType for a handful of flat query parameters and @InputType for cohesive mutation payloads.
  • Register custom scalars like DateTime and JSON once in GraphQLModule, and treat JSON as a last resort.
Last updated June 14, 2026
Was this helpful?