From 1312584998b005b30a51b7ee528e0ad4bb6e6b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Tue, 23 Apr 2019 21:28:46 +0200 Subject: [PATCH] release: 0.17.2 --- CHANGELOG.md | 5 +- package-lock.json | 2 +- package.json | 2 +- website/i18n/en.json | 20 ++ .../versioned_docs/version-0.17.2/examples.md | 44 ++++ .../version-0.17.2/getting-started.md | 190 ++++++++++++++++++ .../version-0.17.2/interfaces.md | 74 +++++++ .../version-0.17.2/introduction.md | 59 ++++++ .../version-0.17.2/types-and-fields.md | 125 ++++++++++++ .../versioned_docs/version-0.17.2/unions.md | 109 ++++++++++ website/versions.json | 1 + 11 files changed, 627 insertions(+), 4 deletions(-) create mode 100644 website/versioned_docs/version-0.17.2/examples.md create mode 100644 website/versioned_docs/version-0.17.2/getting-started.md create mode 100644 website/versioned_docs/version-0.17.2/interfaces.md create mode 100644 website/versioned_docs/version-0.17.2/introduction.md create mode 100644 website/versioned_docs/version-0.17.2/types-and-fields.md create mode 100644 website/versioned_docs/version-0.17.2/unions.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c913ef27..084f7cc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Changelog and release notes -## Unreleased + +## v0.17.2 ### Features - add support for defining `resolveType` function for interfaces and unions (#319) -- add postinstall script for printing info on console about supporting the project - add support for setting default nullability for fields and return types (#297) - add `skipCheck` option in `buildSchema` to disable checking the correctness of a schema +- add postinstall script for printing info on console about supporting the project ### Fixes - fix generating plain resolvers for queries and mutations (compatibility with Apollo client state) diff --git a/package-lock.json b/package-lock.json index b41440622..01356ca11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "type-graphql", - "version": "0.17.1", + "version": "0.17.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 89052e829..e5baf016e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "type-graphql", - "version": "0.17.1", + "version": "0.17.2", "author": { "name": "Michał Lytek", "url": "https://github.com/19majkel94" diff --git a/website/i18n/en.json b/website/i18n/en.json index 361da9d38..4875c853f 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -274,6 +274,26 @@ "version-0.17.1-validation": { "title": "Argument and Input validation", "sidebar_label": "Validation" + }, + "version-0.17.2-examples": { + "title": "Examples", + "sidebar_label": "List of examples" + }, + "version-0.17.2-getting-started": { + "title": "Getting started" + }, + "version-0.17.2-interfaces": { + "title": "Interfaces" + }, + "version-0.17.2-introduction": { + "title": "Introduction", + "sidebar_label": "What & Why" + }, + "version-0.17.2-types-and-fields": { + "title": "Types and Fields" + }, + "version-0.17.2-unions": { + "title": "Unions" } }, "links": { diff --git a/website/versioned_docs/version-0.17.2/examples.md b/website/versioned_docs/version-0.17.2/examples.md new file mode 100644 index 000000000..a3da714b3 --- /dev/null +++ b/website/versioned_docs/version-0.17.2/examples.md @@ -0,0 +1,44 @@ +--- +title: Examples +sidebar_label: List of examples +id: version-0.17.2-examples +original_id: examples +--- + +On the [GitHub repository](https://github.com/19majkel94/type-graphql) there are a few simple examples of how to use different TypeGraphQL features and how well they integrate with 3rd party libraries. + +All examples have an `examples.gql` file with sample queries/mutations/subscriptions that we can execute. + +## Basics + +- [Simple usage of fields, basic types and resolvers](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/simple-usage) + +## Advanced + +- [Enums and unions](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/enums-and-unions) +- [Subscriptions (simple)](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/simple-subscriptions) +- [Subscriptions (using Redis)](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/redis-subscriptions) +- [Interfaces](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/interfaces-inheritance) + +## Features usage + +- [Dependency injection (IoC container)](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/using-container) + - [scoped container](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/using-scoped-container) +- [Authorization](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/authorization) +- [Validation](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/automatic-validation) +- [Types inheritance](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/interfaces-inheritance) +- [Resolvers inheritance](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/resolvers-inheritance) +- [Generic types](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/generic-types) +- [Middlewares](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/middlewares) + +## 3rd party libs integration + +- [TypeORM (manual, synchronous) \*](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/typeorm-basic-usage) +- [TypeORM (automatic, lazy relations) \*](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/typeorm-lazy-relations) +- [Typegoose](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/typegoose) +- [Apollo Engine (Apollo Cache Control) \*\*](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/apollo-engine) +- [Apollo client state](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/apollo-client) + +_\* Note that we need to edit the TypeORM example's `index.ts` with the credentials of our local database_ + +_\*\* Note that we need to provide an `APOLLO_ENGINE_API_KEY` env variable with our own API key_ diff --git a/website/versioned_docs/version-0.17.2/getting-started.md b/website/versioned_docs/version-0.17.2/getting-started.md new file mode 100644 index 000000000..58e9ff5ca --- /dev/null +++ b/website/versioned_docs/version-0.17.2/getting-started.md @@ -0,0 +1,190 @@ +--- +title: Getting started +id: version-0.17.2-getting-started +original_id: getting-started +--- + +> Make sure you've completed all the steps described in the [installation instructions](installation.md). + +To explore all of the powerful capabilities of TypeGraphQL, we will create a sample GraphQL API for cooking recipes. + +Let's start with the `Recipe` type, which is the foundation of our API. + +## Types + +Our goal is to get the equivalent of this type described in SDL: + +```graphql +type Recipe { + id: ID! + title: String! + description: String + creationDate: Date! + ingredients: [String!]! +} +``` + +So we create the `Recipe` class with all its properties and types: + +```typescript +class Recipe { + id: string; + title: string; + description?: string; + creationDate: Date; + ingredients: string[]; +} +``` + +Then we decorate the class and its properties with decorators: + +```typescript +@ObjectType() +class Recipe { + @Field(type => ID) + id: string; + + @Field() + title: string; + + @Field({ nullable: true }) + description?: string; + + @Field() + creationDate: Date; + + @Field(type => [String]) + ingredients: string[]; +} +``` + +The detailed rules of when to use `nullable`, `array` and others are described in the [fields and types docs](types-and-fields.md). + +## Resolvers + +After that we want to create typical crud queries and mutations. To do so, we create the resolver (controller) class that will have injected the `RecipeService` in the constructor: + +```typescript +@Resolver(Recipe) +class RecipeResolver { + constructor(private recipeService: RecipeService) {} + + @Query(returns => Recipe) + async recipe(@Arg("id") id: string) { + const recipe = await this.recipeService.findById(id); + if (recipe === undefined) { + throw new RecipeNotFoundError(id); + } + return recipe; + } + + @Query(returns => [Recipe]) + recipes(@Args() { skip, take }: RecipesArgs) { + return this.recipeService.findAll({ skip, take }); + } + + @Mutation(returns => Recipe) + @Authorized() + addRecipe( + @Arg("newRecipeData") newRecipeData: NewRecipeInput, + @Ctx("user") user: User, + ): Promise { + return this.recipeService.addNew({ data: newRecipeData, user }); + } + + @Mutation(returns => Boolean) + @Authorized(Roles.Admin) + async removeRecipe(@Arg("id") id: string) { + try { + await this.recipeService.removeById(id); + return true; + } catch { + return false; + } + } +} +``` + +We use the `@Authorized()` decorator to restrict access to authorized users only or the users that fulfil the roles requirements. +The detailed rules for when and why we declare `returns => Recipe` functions and others are described in [resolvers docs](resolvers.md). + +## Inputs and Arguments + +Ok, but what are `NewRecipeInput` and `RecipesArgs`? They are of course classes: + +```typescript +@InputType() +class NewRecipeDataInput { + @Field() + @MaxLength(30) + title: string; + + @Field({ nullable: true }) + @Length(30, 255) + description?: string; + + @Field(type => [String]) + @MaxArraySize(30) + ingredients: string[]; +} + +@ArgsType() +class RecipesArgs { + @Field(type => Int) + @Min(0) + skip: number = 0; + + @Field(type => Int) + @Min(1) + @Max(50) + take: number = 25; +} +``` + +`@Length`, `@Min` and `@MaxArraySize` are decorators from [`class-validator`](https://github.com/typestack/class-validator) that automatically perform field validation in TypeGraphQL. + +## Building schema + +The last step that needs to be done is to actually build the schema from the TypeGraphQL definition. We use the `buildSchema` function for this: + +```typescript +const schema = await buildSchema({ + resolvers: [RecipeResolver], +}); + +// ...creating express server or sth +``` + +Et voilà! Now we have fully functional GraphQL schema! +If we print it, this is how it would look: + +```graphql +type Recipe { + id: ID! + title: String! + description: String + creationDate: Date! + ingredients: [String!]! +} +input NewRecipeInput { + title: String! + description: String + ingredients: [String!]! +} +type Query { + recipe(id: ID!): Recipe + recipes(skip: Int = 0, take: Int = 25): [Recipe!]! +} +type Mutation { + addRecipe(newRecipeData: NewRecipeInput!): Recipe! + removeRecipe(id: ID!): Boolean! +} +``` + +## Want more? + +That was only the tip of the iceberg - a very simple example with basic GraphQL types. Do you use interfaces, enums, unions and custom scalars? That's great because TypeGraphQL fully supports them too! There are also more advanced concepts like the authorization checker, inheritance support and field resolvers. + +A lot of these topics are covered in Ben Awad's [Ben Awad](https://github.com/benawad)'s [TypeGraphQL video series](https://www.youtube.com/playlist?list=PLN3n1USn4xlma1bBu3Tloe4NyYn9Ko8Gs) on YouTube. + +For more complicated cases, go to the [Examples section](examples.md) where you can discover e.g. how well TypeGraphQL integrates with TypeORM. diff --git a/website/versioned_docs/version-0.17.2/interfaces.md b/website/versioned_docs/version-0.17.2/interfaces.md new file mode 100644 index 000000000..cf8a4492a --- /dev/null +++ b/website/versioned_docs/version-0.17.2/interfaces.md @@ -0,0 +1,74 @@ +--- +title: Interfaces +id: version-0.17.2-interfaces +original_id: interfaces +--- + +The main idea of TypeGraphQL is to create GraphQL types based on TypeScript classes. + +In object-oriented programming it is common to create interfaces which describe the contract that classes implementing them must adhere to. Hence, TypeGraphQL supports defining GraphQL interfaces. + +Read more about the GraphQL Interface Type in the [official GraphQL docs](https://graphql.org/learn/schema/#interfaces). + +## Usage + +TypeScript has first class support for interfaces. Unfortunately, they only exist at compile-time, so we can't use them to build GraphQL schema at runtime by using decorators. + +Luckily, we can use an abstract class for this purpose. It behaves almost like an interface - it can't be "newed" but it can be implemented by the class - and it just won't prevent developers from implementing a method or initializing a field. So, as long as we treat it like an interface, we can safely use it. + +How do we create a GraphQL interface definition? We create an abstract class and decorate it with the `@InterfaceType()` decorator. The rest is exactly the same as with object types: we use the `@Field` decorator to declare the shape of the type: + +```typescript +@InterfaceType() +abstract class IPerson { + @Field(type => ID) + id: string; + + @Field() + name: string; + + @Field(type => Int) + age: number; +} +``` + +We can then we use this "interface" in the object type class definition: + +```typescript +@ObjectType({ implements: IPerson }) +class Person implements IPerson { + id: string; + name: string; + age: number; +} +``` + +The only difference is that we have to let TypeGraphQL know that this `ObjectType` is implementing the `InterfaceType`. We do this by passing the param `({ implements: IPerson })` to the decorator. If we implement multiple interfaces, we pass an array of interfaces like so: `({ implements: [IPerson, IAnimal, IMachine] })`. + +We can also omit the decorators since the GraphQL types will be copied from the interface definition - this way we won't have to maintain two definitions and solely rely on TypeScript type checking for correct interface implementation. + +## Resolving Type + +Be aware that when our object type is implementing a GraphQL interface type, **we have to return an instance of the type class** in our resolvers. Otherwise, `graphql-js` will not be able to detect the underlying GraphQL type correctly. + +We can also provide our own `resolveType` function implementation to the `@InterfaceType` options. This way we can return plain objects in resolvers and then determine the returned object type by checking the shape of the data object, the same ways [like in unions](./unions.md), e.g.: + +```typescript +@InterfaceType({ + resolveType: value => { + if ("grades" in value) { + return "Student"; // schema name of the type as a string + } + return Person; // or the object type class + }, +}) +abstract class IPerson { + // ... +} +``` + +However in case of interfaces, it might be a little bit more tricky than with unions, as we might not remember all the object types that implements this particular interface. + +## Examples + +For more advanced usage examples of interfaces (and type inheritance), e.g. with query returning an interface type, go to [this examples folder](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/interfaces-inheritance). diff --git a/website/versioned_docs/version-0.17.2/introduction.md b/website/versioned_docs/version-0.17.2/introduction.md new file mode 100644 index 000000000..4eb64ef51 --- /dev/null +++ b/website/versioned_docs/version-0.17.2/introduction.md @@ -0,0 +1,59 @@ +--- +title: Introduction +sidebar_label: What & Why +id: version-0.17.2-introduction +original_id: introduction +--- + +We all love GraphQL! It's really great and solves many problems that we have with REST APIs, such as overfetching and underfetching. But developing a GraphQL API in Node.js with TypeScript is sometimes a bit of a pain. + +## What? + +**TypeGraphQL** is a library that makes this process enjoyable by defining the schema using only classes and a bit of decorator magic. +Example object type: + +```typescript +@ObjectType() +class Recipe { + @Field() + title: string; + + @Field(type => [Rate]) + ratings: Rate[]; + + @Field({ nullable: true }) + averageRating?: number; +} +``` + +It also has a set of useful features, like validation, authorization and dependency injection, which helps develop GraphQL APIs quickly & easily! + +## Why? + +As mentioned, developing a GraphQL API in Node.js with TypeScript is sometimes a bit of a pain. +Why? Let's take a look at the steps we usually have to take. + +First, we create all the schema types in SDL. We also create our data models using [ORM classes](https://github.com/typeorm/typeorm), which represent our database entities. Then we start to write resolvers for our queries, mutations and fields. This forces us, however, to begin with creating TypeScript interfaces for all arguments and inputs and/or object types. After that, we can actually implement the resolvers, using weird generic signatures, e.g.: + +```typescript +export const getRecipesResolver: GraphQLFieldResolver = async ( + _, + args, + ctx, +) => { + // common tasks repeatable for almost every resolver + const auth = Container.get(AuthService); + if (!auth.check(ctx.user)) { + throw new NotAuthorizedError(); + } + await joi.validate(getRecipesSchema, args); + const repository = TypeORM.getRepository(Recipe); + + // our business logic, e.g.: + return repository.find({ skip: args.offset, take: args.limit }); +}; +``` + +The biggest problem is code redundancy which makes it difficult to keep things in sync. To add a new field to our entity, we have to jump through all the files: modify the entity class, then modify the schema, and finally update the interface. The same goes with inputs or arguments: it's easy to forget to update one of them or make a mistake with a type. Also, what if we've made a typo in a field name? The rename feature (F2) won't work correctly. + +**TypeGraphQL** comes to address these issues, based on experience from over a year of developing GraphQL APIs in TypeScript. The main idea is to have only one source of truth by defining the schema using classes and a bit of decorator help. Additional features like dependency injection, validation and auth guards help with common tasks that would normally have to be handled by ourselves. diff --git a/website/versioned_docs/version-0.17.2/types-and-fields.md b/website/versioned_docs/version-0.17.2/types-and-fields.md new file mode 100644 index 000000000..f7b3b6fbc --- /dev/null +++ b/website/versioned_docs/version-0.17.2/types-and-fields.md @@ -0,0 +1,125 @@ +--- +title: Types and Fields +id: version-0.17.2-types-and-fields +original_id: types-and-fields +--- + +The main idea of TypeGraphQL is to automatically create GraphQL schema definitions from TypeScript classes. To avoid the need for schema definition files and interfaces describing the schema, we use decorators and a bit of reflection magic. + +Let's start by defining our example TypeScript class which represents our `Recipe` model with fields for storing the recipe data: + +```typescript +class Recipe { + id: string; + title: string; + ratings: Rate[]; + averageRating?: number; +} +``` + +The first thing we must do is decorate the class with the `@ObjectType` decorator. It marks the class as the `type` known from the GraphQL SDL or `GraphQLObjectType` from `graphql-js`: + +```typescript +@ObjectType() +class Recipe { + id: string; + title: string; + ratings: Rate[]; + averageRating: number; +} +``` + +Then we declare which class properties should be mapped to the GraphQL fields. +To do this, we use the `@Field` decorator, which is also used to collect metadata from the TypeScript reflection system: + +```typescript +@ObjectType() +class Recipe { + @Field() + id: string; + + @Field() + title: string; + + @Field() + ratings: Rate[]; + + @Field() + averageRating: number; +} +``` + +For simple types (like `string` or `boolean`) this is all that's needed but due to a limitation in TypeScript's reflection, we need to provide info about generic types (like `Array` or `Promise`). So to declare the `Rate[]` type, we have two options available: + +- `@Field(type => [Rate])` (recommended, explicit `[ ]` syntax for Array types) +- `@Field(itemType => Rate)` (`array` is inferred from reflection - also works but is prone to errors) + +Why use function syntax and not a simple `{ type: Rate }` config object? Because, by using function syntax we solve the problem of 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. + +By default, all fields are non nullable, just like properties in TypeScript. However, you can change that behavior by providing `nullableByDefault: true` option in `buildSchema` settings, described in [bootstrap guide](./bootstrap.md). + +So for nullable properties like `averageRating` which might not be defined when a recipe has no ratings yet, we mark the class property as optional with a `?:` operator and also have to pass the `{ nullable: true }` decorator parameter. We should be aware that when we declare our type as a nullable union (e.g. `string | null`), we need to explicitly provide the type to the `@Field` decorator. + +In the case of lists, we may also need to define their nullability in a more detailed form. The basic `{ nullable: true | false }` setting only applies to the whole list (`[Item!]` or `[Item!]!`), so if we need a sparse array, we can control the list items' nullability via `nullable: items` (for `[Item]!`) or `nullable: itemsAndList` (for the `[Item]`) option. Be aware that setting `nullableByDefault: true` option will also apply to lists, so it will produce `[Item]` type, just like with `nullable: itemsAndList`. + +In the config object we can also provide the `description` and `deprecationReason` properties for GraphQL schema purposes. + +So after these changes our example class would look like this: + +```typescript +@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 will result in generating the following part of the GraphQL schema in SDL: + +```graphql +type Recipe { + id: ID! + title: String! + ratings: [Rate!]! + averageRating: Float +} +``` + +Similarly, the `Rate` type class would look like this: + +```typescript +@ObjectType() +class Rate { + @Field(type => Int) + value: number; + + @Field() + date: Date; + + user: User; +} +``` + +which results in this equivalent of the GraphQL SDL: + +```graphql +type Rate { + value: Int! + date: Date! +} +``` + +As we can see, for the `id` property of `Recipe` we passed `type => ID` and for the `value` field of `Rate` we passed `type => Int`. This way we can overwrite the inferred type from the reflection metadata. We can read more about the ID and Int scalars in [the scalars docs](scalars.md). There is also a section about the built-in `Date` scalar. + +Also the `user` property doesn't have a `@Field()` decorator - this way we can hide some properties of our data model. In this case, we need to store the `user` field of the `Rate` object to the database in order to prevent multiple rates, but we don't want to make it publicly accessible. + +Note that if a field of an object type is purely calculable (e.g. `averageRating` from `ratings` array) and we don't want to pollute the class signature, we can omit it and just implement the field resolver (described in [resolvers doc](resolvers.md)). diff --git a/website/versioned_docs/version-0.17.2/unions.md b/website/versioned_docs/version-0.17.2/unions.md new file mode 100644 index 000000000..df661e292 --- /dev/null +++ b/website/versioned_docs/version-0.17.2/unions.md @@ -0,0 +1,109 @@ +--- +title: Unions +id: version-0.17.2-unions +original_id: unions +--- + +Sometimes our API has to be flexible and return a type that is not specific but one from a range of possible types. An example might be a movie site's search functionality: using the provided phrase we search the database for movies but also actors. So the query has to return a list of `Movie` or `Actor` types. + +Read more about the GraphQL Union Type in the [official GraphQL docs](http://graphql.org/learn/schema/#union-types). + +## Usage + +Let's start by creating the object types from the example above: + +```typescript +@ObjectType() +class Movie { + @Field() + name: string; + + @Field() + rating: number; +} +``` + +```typescript +@ObjectType() +class Actor { + @Field() + name: string; + + @Field(type => Int) + age: number; +} +``` + +Now let's create a union type from the object types above: + +```typescript +import { createUnionType } from "type-graphql"; + +const SearchResultUnion = createUnionType({ + name: "SearchResult", // the name of the GraphQL union + types: [Movie, Actor], // array of object types classes +}); +``` + +Now we can use the union type in the query. +Notice, that due to TypeScript's reflection limitation, we have to explicitly use the `SearchResultUnion` value in the `@Query` decorator return type annotation. +For TypeScript compile-time type safety we can also use `typeof SearchResultUnion` which is equal to type `Movie | Actor`. + +```typescript +@Resolver() +class SearchResolver { + @Query(returns => [SearchResultUnion]) + async search(@Arg("phrase") phrase: string): Promise> { + const movies = await Movies.findAll(phrase); + const actors = await Actors.findAll(phrase); + + return [...movies, ...actors]; + } +} +``` + +## Resolving Type + +Be aware that when the query/mutation return type (or field type) is a union, we have to return a specific instance of the object type class. Otherwise, `graphql-js` will not be able to detect the underlying GraphQL type correctly when we use plain JS objects. + +However, we can also provide our own `resolveType` function implementation to the `createUnionType` options. This way we can return plain objects in resolvers and then determine the returned object type by checking the shape of the data object, e.g.: + +```typescript +const SearchResultUnion = createUnionType({ + name: "SearchResult", + types: [Movie, Actor], + // our implementation of detecting returned object type + resolveType: value => { + if ("rating" in value) { + return Movie; // we can return object type class (the one with `@ObjectType()`) + } + if ("age" in value) { + return "Actor"; // or the schema name of the type as a string + } + return undefined; + }, +}); +``` + +**Et Voilà!** We can now build the schema and make the example query 😉 + +```graphql +query { + search(phrase: "Holmes") { + ... on Actor { + # maybe Katie Holmes? + name + age + } + ... on Movie { + # for sure Sherlock Holmes! + name + rating + } + } +} +``` + +## Examples + +More advanced usage examples of unions (and enums) are located in [this examples folder](https://github.com/19majkel94/type-graphql/tree/v0.17.2/examples/enums-and-unions). diff --git a/website/versions.json b/website/versions.json index acaad1b57..c7b5e2872 100644 --- a/website/versions.json +++ b/website/versions.json @@ -1,4 +1,5 @@ [ + "0.17.2", "0.17.1", "0.17.0", "0.16.0"