diff --git a/CHANGELOG.md b/CHANGELOG.md index 96095a73b..bef294548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # Changelog and release notes -## Unreleased + + +## v1.1.0 ### Features - allow passing custom validation function as `validate` option to `buildSchema` - support defining deprecation reason and description of enum members (#714) diff --git a/website/versioned_docs/version-1.1.0/enums.md b/website/versioned_docs/version-1.1.0/enums.md new file mode 100644 index 000000000..baaf3f83b --- /dev/null +++ b/website/versioned_docs/version-1.1.0/enums.md @@ -0,0 +1,143 @@ +--- +title: Enums +id: version-1.1.0-enums +original_id: enums +--- + +Nowadays almost all typed languages have support for enumerated types, including TypeScript. Enums limit the range of a variable's values to a set of predefined constants, which makes it easier to document intent. + +GraphQL also has enum type support, so TypeGraphQL allows us to use TypeScript enums in our GraphQL schema. + +## Creating enum + +Let's create a TypeScript enum. It can be a numeric or string enum - the internal values of enums are taken from the enum definition values and the public names taken from the enum keys: + +```typescript +// implicit value 0, 1, 2, 3 +enum Direction { + UP, + DOWN, + LEFT, + RIGHT, +} + +// or explicit values +enum Direction { + UP = "up", + DOWN = "down", + LEFT = "left", + RIGHT = "right", +} +``` + +To tell TypeGraphQL about our enum, we would ideally mark the enums with the `@EnumType()` decorator. However, TypeScript decorators only work with classes, so we need to make TypeGraphQL aware of the enums manually by calling the `registerEnumType` function and providing the enum name for GraphQL: + +```typescript +import { registerEnumType } from "type-graphql"; + +registerEnumType(Direction, { + name: "Direction", // this one is mandatory + description: "The basic directions", // this one is optional +}); +``` + +In case we need to provide additional GraphQL-related config for values, like description or deprecation reason, we can use `valuesConfig` property and put the data inside it, e.g.: + +```typescript +enum Direction { + UP = "UP", + DOWN = "DOWN", + LEFT = "LEFT", + RIGHT = "RIGHT", + SIDEWAYS = "SIDEWAYS", +} + +registerEnumType(Direction, { + name: "Direction", + description: "The basic directions", + valuesConfig: { + SIDEWAYS: { + deprecationReason: "Replaced with Left or Right", + }, + RIGHT: { + description: "The other left", + }, + }, +}); +``` + +This way, the additional info will be emitted in the GraphQL schema: + +```graphql +enum Direction { + UP + DOWN + LEFT + """ + The other left + """ + RIGHT + SIDEWAYS @deprecated(reason: "Replaced with Left or Right") +} +``` + +## Using enum + +The last step is very important: TypeScript has limited reflection ability, so this is a case where we have to explicitly provide the enum type for object type fields, input type fields, args, and the return type of queries and mutations: + +```typescript +@InputType() +class JourneyInput { + @Field(type => Direction) // it's very important + direction: Direction; +} +``` + +Without this annotation, the generated GQL type would be `String` or `Float` (depending on the enum type), rather than the `ENUM` we are aiming for. + +With all that in place, we can use our enum directly in our code 😉 + +```typescript +@Resolver() +class SpriteResolver { + private sprite = getMarioSprite(); + + @Mutation() + move(@Arg("direction", type => Direction) direction: Direction): boolean { + switch (direction) { + case Direction.Up: + this.sprite.position.y++; + break; + case Direction.Down: + this.sprite.position.y--; + break; + case Direction.Left: + this.sprite.position.x--; + break; + case Direction.Right: + this.sprite.position.x++; + break; + default: + // it will never be hitten ;) + return false; + } + + return true; + } +} +``` + +## Interoperability + +Enums in TypeGraphQL are designed with server side in mind - the runtime will map the string value from input into a corresponding enum value, like `"UP"` into `0`. While this is very handy e.g. for mapping database values into GraphQL API enum names, it makes it unusable on the query side because `Direction.UP` will put `0` in the query which is an invalid value (should be `UP`). + +So if we would like to share the types definition and use the enum on the client side app or use the enums directly on the server app e.g. in tests, we have to use the direct mapping of the enum member names with values, e.g.: + +```typescript +enum Direction { + UP = "UP", + DOWN = "DOWN", + LEFT = "LEFT", + RIGHT = "RIGHT", +} +``` diff --git a/website/versioned_docs/version-1.1.0/examples.md b/website/versioned_docs/version-1.1.0/examples.md new file mode 100644 index 000000000..4ae0d4624 --- /dev/null +++ b/website/versioned_docs/version-1.1.0/examples.md @@ -0,0 +1,51 @@ +--- +title: Examples +sidebar_label: List of examples +id: version-1.1.0-examples +original_id: examples +--- + +On the [GitHub repository](https://github.com/MichalLytek/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/MichalLytek/type-graphql/tree/v1.1.0/examples/simple-usage) + +## Advanced + +- [Enums and unions](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/enums-and-unions) +- [Subscriptions (simple)](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/simple-subscriptions) +- [Subscriptions (using Redis)](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/redis-subscriptions) +- [Interfaces](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/interfaces-inheritance) +- [Extensions (metadata)](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/extensions) + +## Features usage + +- [Dependency injection (IoC container)](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/using-container) + - [Scoped containers](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/using-scoped-container) +- [Authorization](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/authorization) +- [Validation](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/automatic-validation) + - [Custom validation](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/custom-validation) +- [Types inheritance](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/interfaces-inheritance) +- [Resolvers inheritance](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/resolvers-inheritance) +- [Generic types](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/generic-types) +- [Mixin classes](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/mixin-classes) +- [Middlewares and Custom Decorators](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/middlewares-custom-decorators) +- [Query complexity](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/query-complexity) + +## 3rd party libs integration + +- [TypeORM (manual, synchronous) \*](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/typeorm-basic-usage) +- [TypeORM (automatic, lazy relations) \*](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/typeorm-lazy-relations) +- [MikroORM](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/mikro-orm) +- [Typegoose](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/typegoose) +- [Apollo federation](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/apollo-federation) +- [Apollo Engine (Apollo Cache Control) \*\*](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/apollo-engine) +- [Apollo client state](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/apollo-client) +- [GraphQL Modules](https://github.com/MichalLytek/type-graphql/tree/v1.1.0/examples/graphql-modules) + +_\* 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-1.1.0/prisma.md b/website/versioned_docs/version-1.1.0/prisma.md new file mode 100644 index 000000000..0f58a2a90 --- /dev/null +++ b/website/versioned_docs/version-1.1.0/prisma.md @@ -0,0 +1,55 @@ +--- +title: Prisma 2 Integration +sidebar_label: Prisma 2 +id: version-1.1.0-prisma +original_id: prisma +--- + +TypeGraphQL provides an integration with Prisma 2 by the [`typegraphql-prisma` package](https://www.npmjs.com/package/typegraphql-prisma). + +It generates the type classes and CRUD resolvers based on the Prisma schema, so you can execute complex queries or mutations that corresponds to the Prisma actions, without having to write any code for that. + +## Overview + +To make use of the prisma integration, first you need to add a new generator to the `schema.prisma` file: + +```sh +generator typegraphql { + provider = "typegraphql-prisma" + output = "../src/generated/typegraphql-prisma" +} +``` + +Then, after running `prisma generate` you can import the generated classes and use them to build your schema: + +```typescript +import { User, UserRelationsResolver, UserCrudResolver } from "./generated/typegraphql-prisma"; + +const schema = await buildSchema({ + resolvers: [CustomUserResolver, UserRelationsResolver, UserCrudResolver], + validate: false, +}); +``` + +So you will be able to execute such complex query that talks with the db in just a few minutes! + +```graphql +query GetSomeUsers { + users(where: { email: { contains: "prisma" } }, orderBy: { name: desc }) { + id + name + email + posts(take: 10, orderBy: { updatedAt: desc }) { + published + title + content + } + } +} +``` + +## Documentation and examples + +To read about all the `typegraphql-prisma` features, like exposing selected Prisma actions or changing exposed model type name, as well as how to write a custom query or how to add some fields to model type, please check the docs [on the separate GitHub repository](https://github.com/MichalLytek/typegraphql-prisma/blob/main/Readme.md). + +You can find there also some examples and more detailed info about the installation and the configuration. diff --git a/website/versioned_docs/version-1.1.0/validation.md b/website/versioned_docs/version-1.1.0/validation.md new file mode 100644 index 000000000..df630d21b --- /dev/null +++ b/website/versioned_docs/version-1.1.0/validation.md @@ -0,0 +1,221 @@ +--- +title: Argument and Input validation +sidebar_label: Validation +id: version-1.1.0-validation +original_id: validation +--- + +## Scalars + +The standard way to ensure that inputs and arguments are correct, such as an `email` field that really contains a proper e-mail address, is to use [custom scalars](https://github.com/MichalLytek/type-graphql/blob/master/docs/scalars.md) e.g. `GraphQLEmail` from [`graphql-custom-types`](https://github.com/stylesuxx/graphql-custom-types). However, creating scalars for all single cases of data types (credit card number, base64, IP, URL) might be cumbersome. + +That's why TypeGraphQL has built-in support for argument and input validation. +By default, we can use the [`class-validator`](https://github.com/typestack/class-validator) library and easily declare the requirements for incoming data (e.g. a number is in the range 0-255 or a password that is longer than 8 characters) thanks to the awesomeness of decorators. + +We can also use other libraries or our own custom solution, as described in [custom validators](#custom-validators) section. + +## `class-validator` + +### How to use + +First we decorate the input/arguments class with the appropriate decorators from `class-validator`. +So we take this: + +```typescript +@InputType() +export class RecipeInput { + @Field() + title: string; + + @Field({ nullable: true }) + description?: string; +} +``` + +...and turn it into this: + +```typescript +import { MaxLength, Length } from "class-validator"; + +@InputType() +export class RecipeInput { + @Field() + @MaxLength(30) + title: string; + + @Field({ nullable: true }) + @Length(30, 255) + description?: string; +} +``` + +And that's it! 😉 + +TypeGraphQL will automatically validate our inputs and arguments based on the definitions: + +```typescript +@Resolver(of => Recipe) +export class RecipeResolver { + @Mutation(returns => Recipe) + async addRecipe(@Arg("input") recipeInput: RecipeInput): Promise { + // you can be 100% sure that the input is correct + console.assert(recipeInput.title.length <= 30); + console.assert(recipeInput.description.length >= 30); + console.assert(recipeInput.description.length <= 255); + } +} +``` + +Of course, [there are many more decorators](https://github.com/typestack/class-validator#validation-decorators) we have access to, not just the simple `@Length` decorator used in the example above, so take a look at the `class-validator` documentation. + +This feature is enabled by default. However, we can disable it if we must: + +```typescript +const schema = await buildSchema({ + resolvers: [RecipeResolver], + validate: false, // disable automatic validation or pass the default config object +}); +``` + +And we can still enable it per resolver's argument if we need to: + +```typescript +class RecipeResolver { + @Mutation(returns => Recipe) + async addRecipe(@Arg("input", { validate: true }) recipeInput: RecipeInput) { + // ... + } +} +``` + +The `ValidatorOptions` object used for setting features like [validation groups](https://github.com/typestack/class-validator#validation-groups) can also be passed: + +```typescript +class RecipeResolver { + @Mutation(returns => Recipe) + async addRecipe( + @Arg("input", { validate: { groups: ["admin"] } }) + recipeInput: RecipeInput, + ) { + // ... + } +} +``` + +Note that by default, the `skipMissingProperties` setting of the `class-validator` is set to `true` because GraphQL will independently check whether the params/fields exist or not. + +GraphQL will also check whether the fields have correct types (String, Int, Float, Boolean, etc.) so we don't have to use the `@IsOptional`, `@Allow`, `@IsString` or the `@IsInt` decorators at all! + +However, when using nested input or arrays, we always have to use [`@ValidateNested()` decorator](https://github.com/typestack/class-validator#validating-nested-objects) or [`{ each: true }` option](https://github.com/typestack/class-validator#validating-arrays) to make nested validation work properly. + +### Response to the Client + +When a client sends incorrect data to the server: + +```graphql +mutation ValidationMutation { + addRecipe( + input: { + # too long! + title: "Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet" + } + ) { + title + creationDate + } +} +``` + +the [`ArgumentValidationError`](https://github.com/MichalLytek/type-graphql/blob/master/src/errors/ArgumentValidationError.ts) will be thrown. + +By default, the `apollo-server` package from the [bootstrap guide](bootstrap.md) will format the error to match the `GraphQLFormattedError` interface. So when the `ArgumentValidationError` occurs, the client will receive this JSON with a nice `validationErrors` property inside of `extensions.exception`: + +```json +{ + "errors": [ + { + "message": "Argument Validation Error", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": ["addRecipe"], + "extensions": { + "code": "INTERNAL_SERVER_ERROR", + "exception": { + "validationErrors": [ + { + "target": { + "title": "Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet" + }, + "value": "Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet", + "property": "title", + "children": [], + "constraints": { + "maxLength": "title must be shorter than or equal to 30 characters" + } + } + ], + "stacktrace": [ + "Error: Argument Validation Error", + " at Object. (F:\\#Projekty\\type-graphql\\src\\resolvers\\validate-arg.ts:29:11)", + " at Generator.throw ()", + " at rejected (F:\\#Projekty\\type-graphql\\node_modules\\tslib\\tslib.js:105:69)", + " at processTicksAndRejections (internal/process/next_tick.js:81:5)" + ] + } + } + } + ], + "data": null +} +``` + +Of course we can also create our own custom implementation of the `formatError` function provided in the `ApolloServer` config options which will transform the `GraphQLError` with a `ValidationError` array in the desired output format (e.g. `extensions.code = "ARGUMENT_VALIDATION_ERROR"`). + +### Example + +To see how this works, check out the [simple real life example](https://github.com/MichalLytek/type-graphql/tree/master/examples/automatic-validation). + +### Caveats + +Even if we don't use the validation feature (and we have provided `{ validate: false }` option to `buildSchema`), we still need to have `class-validator` installed as a dev dependency in order to compile our app without errors using `tsc`. + +An alternative solution that allows to completely get rid off big `class-validator` from our project's `node_modules` folder is to suppress the `error TS2307: Cannot find module 'class-validator'` TS error by providing `"skipLibCheck": true` setting in `tsconfig.json`. + +## Custom validator + +We can also use other libraries than `class-validator` together with TypeGraphQL. + +To integrate it, all we need to do is to provide a custom function as `validate` option in `buildSchema`. +It receives two parameters: + +- `argValue` which is the injected value of `@Arg()` or `@Args()` +- `argType` which is a runtime type information (e.g. `String` or `RecipeInput`). + +The `validate` function can be async and should return nothing (`void`) when validation passes or throw an error when validation fails. +So be aware of this while trying to wrap another library in `validate` function for TypeGraphQL. + +Example using [decorators library for Joi validators (`joiful`)](https://github.com/joiful-ts/joiful): + +```ts +const schema = await buildSchema({ + // ...other options + validate: argValue => { + // call joiful validate + const { error } = joiful.validate(argValue); + if (error) { + // throw error on failed validation + throw error; + } + }, +}); +``` + +> Be aware that when using custom validator, the error won't be wrapped with `ArgumentValidationError` like for the built-in `class-validator` validation. + +### Example + +To see how this works, check out the [simple custom validation integration example](https://github.com/MichalLytek/type-graphql/tree/master/examples/custom-validation). diff --git a/website/versions.json b/website/versions.json index 0d2b0802a..63e076a52 100644 --- a/website/versions.json +++ b/website/versions.json @@ -1,4 +1,5 @@ [ + "1.1.0", "1.0.0", "0.17.6", "0.17.5",