Types and fields
The main idea of TypeGraphQL is to automatically create GraphQL schema definition from TypeScript's classes. To avoid the need of schema definition files and interfaces describing the schema, we use a bit of reflection magic and decorators.
Let's start with defining the example TypeScript class. It will represent our Recipe
model with fields storing recipe's data:
class Recipe {
id: string;
title: string;
ratings: Rate[];
averageRating?: number;
}
First what we have to do is to decorate the class with e.g. @ObjectType
decorator. It marks the class as the type
known from GraphQL SDL or GraphQLObjectType
from graphql-js
:
@ObjectType()
class Recipe {
id: string;
title: string;
ratings: Rate[];
averageRating: number;
}
Then we need to declare which class properties should be mapped to GraphQL fields.
To do this, we use @Field
decorator, which is also used to collect the metadata from TypeScript reflection system:
@ObjectType()
class Recipe {
@Field()
id: string;
@Field()
title: string;
@Field()
ratings: Rate[];
@Field()
averageRating: number;
}
For simple types (like string
or boolean
) it's enough but unfortunately, due to TypeScript's reflection limitation, we need to provide info about generic types (like Array
or Promise
). So to declare Rate[]
type, there are two options available:
@Field(type => [Rate])
(the recommended way - explicit[ ]
syntax for Array)@Field(itemType => Rate)
(array
is inferred from reflection - also ok but prone to error)
Why function syntax, not simple { type: Rate }
config object? Because this way we solve problems with circular dependencies (e.g. Post <--> User), so it was adopted as a convention. You can use the shorthand syntax @Field(() => Rate)
if you want to save some keystrokes but it might be less readable for others.
For nullable properties like averageRating
(it might be not defined when recipe has no ratings yet), we mark the class property as optional with ?:
operator and also have to pass { nullable: true }
decorator parameter. Be aware that when you declare your type as a nullable union (e.g. string | null
), you need to explicitly provide the type to the @Field
decorator.
In case of lists, you may also need to define the nullability in a more detailed fashion. The basic { nullable: true | false }
settings apply only to the a whole list ([Item!]
or [Item!]!
), so if you need a sparse array, you can control the list items nullability via nullable: items
(for [Item]!
) or nullable: itemsAndList
(for [Item]
) option.
In the config object we can also provide description
and deprecationReason
for GraphQL schema purposes.
So after this changes our example class would look like this:
@ObjectType({ description: "The recipe model" })
class Recipe {
@Field(type => ID)
id: string;
@Field({ description: "The title of the recipe" })
title: string;
@Field(type => [Rate])
ratings: Rate[];
@Field({ nullable: true })
averageRating?: number;
}
Which in result will generate following part of GraphQL schema in SDL:
type Recipe {
id: ID!
title: String!
ratings: [Rate!]!
averageRating: Float
}
Analogously, the Rate
type class would look like this:
@ObjectType()
class Rate {
@Field(type => Int)
value: number;
@Field()
date: Date;
user: User;
}
which results in this equivalent of GraphQL's SDL:
type Rate {
value: Int!
date: Date!
}
As you could see, for id
property of Recipe
we've passed type => ID
and for value
field of Rate
- type => Int
. This way we can overwrite the inferred type from reflection metadata. You can read more about the ID and Int scalars in the scalars docs. There is also a section about the built-in Date
scalar.
Also the user
property doesn't have @Field()
decorator - this way we can hide some properties of our data model. In this case we need to store in database user
info inside Rate
object to prevent multiple rates but we don't want to make it public, accessible to every API consumer.
Note that if a field of an object type is purely calculable (eg. averageRating
from ratings
array) and you don't want to pollute the class signature, you can omit it and just implement the field resolver (described in resolvers doc).