You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+55-47Lines changed: 55 additions & 47 deletions
Original file line number
Diff line number
Diff line change
@@ -3,23 +3,20 @@
3
3
# TypeGraphQL
4
4
Create GraphQL resolvers and schemas with TypeScript!
5
5
6
-
## Work in progress
7
-
Please come back later! :wink:
8
-
However, you can now see the API draft.
6
+
## Design Goals
7
+
We all love GraphQL but creating GraphQL API with TypeScript is a bit of pain.
8
+
We have to mantain separate GQL schemas using SDL or JS API and keep the related TypeScript interfaces in sync with them. We also have separate ORM classes representing our db entities. This duplication is a really bad developer experience.
9
9
10
-
## API Draft
10
+
What if I told you that you can have only one source of truth thanks to a little addition of decorators magic?
11
+
Interested? So take a look at the quick intro to TypeGraphQL!
11
12
13
+
## Getting started
12
14
Let's start at the begining with an example.
13
-
14
15
We have API for cooking recipes and we love using GraphQL for it.
15
-
However, mantaining separate schemas (using SDL) with ORM classes and typescript interfaces is a bad developer experience.
16
-
What if I told you that you can have only one source of truth with a little addition of decorators magic?
17
-
Interested? So take a look at the quick intro to TypeGraphQL!
18
-
19
16
At first we will create the `Recipe` type, which is the foundations of our API:
20
17
21
18
```ts
22
-
@GraphQLType()
19
+
@GraphQLObjectType()
23
20
classRecipe {
24
21
@Field(type=>ID)
25
22
readonly id:string;
@@ -39,9 +36,9 @@ class Recipe {
39
36
```
40
37
Take a look at the decorators:
41
38
42
-
-`@GraphQLType()` marks the class as the `type` shape known from GraphQL SDL
43
-
-`@Field()` marks the property as type field - it is also used to collect type metadata from TypeScript reflection system
44
-
- the parameter in`@Field(type => ID)` is used to declare the GraphQL scalar type like the builit-in `ID`
39
+
-`@GraphQLObjectType()` marks the class as the `object` shape known from GraphQL SDL as `type`
40
+
-`@Field()` marks the property as the object's field - it is also used to collect type metadata from TypeScript reflection system
41
+
- the parameter function in decorator`@Field(type => ID)` is used to declare the GraphQL scalar type like the builit-in `ID`
45
42
- we also have to declare `(type => Rate)` because of limitation of type reflection - emited type of `ratings` property is `Array`, so we need to know what is the type of items in the array
46
43
47
44
This will generate GraphQL type corresponding to this:
@@ -55,10 +52,10 @@ type Recipe {
55
52
}
56
53
```
57
54
58
-
Then, weneedtodefinewhatisthe `Rate` type:
55
+
Next, weneedtodefinewhatisthe `Rate` type:
59
56
60
57
```ts
61
-
@GraphQLType()
58
+
@GraphQLObjectType()
62
59
classRate {
63
60
@Field(type => Int)
64
61
value: number;
@@ -70,39 +67,39 @@ class Rate {
70
67
user: User;
71
68
}
72
69
```
73
-
Again, take a look at `@Field(type => Int)` decorator - Javascript doesn't have integers so we have to mark that our number type will be `Int`, not `Float` (which is number by default).
70
+
Again, take a look at `@Field(type => Int)` decorator - Javascript doesn't have integers so we have to mark that our number type will be `Int`, not `Float` (which is `number` by default).
74
71
75
72
-----
76
73
77
74
So, as we have the base of our recipe related types, let's create a resolver!
78
75
79
76
We will start by creating a class with apropiate decorator:
`@GraphQLResolver` marks our class as a resolver of type `Recipe` (type info is needed for attaching field resolver to correct type). We can also implement `Resolver` interface to make sure that our field resolvers are correct.
83
+
`@GraphQLResolver` marks our class as a resolver of type `Recipe` (type info is needed for attaching field resolver to correct type).
- our query needs to communicate with database, so we declare the repository in constructor and the DI framework will do the magic and injects the instance to our resolver
103
100
- `@Query` decorator marks the class method as the query (who would have thought?)
104
101
- our method is async, so we can't infer the return type from reflection system - we need to define it as `(returnType=>Recipe)` and also mark it as nullable because `findOneById` might not return the recipe (no document with the id in DB)
105
-
- `@Arguments()` marks the parameter as query arguments object, where `FindRecipeArgs` define it's fields - this will be injected in this place to this method
102
+
- `@Args()` marks the parameter as query arguments object, where `FindRecipeArgs` define it's fields - this will be injected in this place to this method
106
103
107
104
So, how the `FindRecipeArgs` looks like?
108
105
```ts
@@ -121,17 +118,19 @@ type Query {
121
118
```
122
119
It is great, isn't it? :smiley:
123
120
124
-
Ok, let's add another one query:
121
+
Ok, let's add another query:
125
122
```ts
126
123
classRecipeResolver {
127
124
// ...
128
-
@Query(Recipe, { array: true })
125
+
@Query(() =>Recipe, { array: true })
129
126
recipes():Promise<Array<Recipe>> {
130
127
returnthis.recipeRepository.find();
131
128
}
132
129
}
133
130
```
134
-
As you can see, the function `@Query(returnType=>Recipe)` is only the convention (that helps resolve circular dependencies btw) and if you want, you can use the shorthand syntax like `@Query(Recipe)` which might be quite less readable for someone. Remember to declare `{ array: true }` if your method is async or returns the `Promise`.
131
+
As you can see, the function parameter name `@Query(returnType=>Recipe)` is only the convention and if you want, you can use the shorthand syntax like `@Query(() =>Recipe)` which might be quite less readable for someone. We need to declare it as a function to help resolve circular dependencies.
132
+
133
+
Also, remember to declare `{ array: true }` when your method is async or returns the `Promise<Array<T>>`.
135
134
136
135
So now we have two queries in our schema:
137
136
```graphql
@@ -145,21 +144,19 @@ Now let's move to the mutations:
145
144
```ts
146
145
classRecipeResolver {
147
146
// ...
148
-
@Mutation(Recipe)
149
-
@Authorized()
147
+
@Mutation(returnType=>Recipe)
150
148
async rate(
151
-
@Argument("rate") rateInput:RateInput,
149
+
@Arg("rate") rateInput:RateInput,
152
150
@Context() { user }:Context,
153
151
) {
154
152
// implementation...
155
153
}
156
154
}
157
155
```
158
-
- we declare the method as mutation using the `@Mutation` with shorthand return type syntax
159
-
- as we allow only logged users to rate the recipe, we declare the `@Authorized()` on the mutation which will revoke access to the mutation for guests (it accepts also the roles as argument for complex authorizations)
160
-
- the `@Argument()` decorator let's you declare single argument of the mutation
156
+
- we declare the method as mutation using the `@Mutation()` with return type function syntax
157
+
- the `@Arg()` decorator let's you declare single argument of the mutation
161
158
- for complex arguments you can use as input types like `RateInput` in this case
162
-
- injecting the context is also possible - using `@Context()` decorator, so you have an access to `request` or `user` data
159
+
- injecting the context is also possible - using `@Context()` decorator, so you have an access to `request` or `user` data - whatever you define on server settings
163
160
164
161
Here's how `RateInput` type looks:
165
162
```ts
@@ -172,7 +169,7 @@ class RateInput {
172
169
value:number;
173
170
}
174
171
```
175
-
`@GraphQLInputType()` marks the class as the `input` in SDL, oposite to `type` or `scalar`
172
+
`@GraphQLInputType()` marks the class as the `input` in SDL, in oposite to `type` or `scalar`
176
173
177
174
The corresponding GraphQL schema:
178
175
```graphql
@@ -189,7 +186,9 @@ type Mutation {
189
186
}
190
187
```
191
188
192
-
The last one we discuss now is the field resolver. Let's assume we store array of ratings in our recipe documents and we want to expose the average rating value.
189
+
The last one we discuss now is the field resolver. As we declared earlier, we store array of ratings in our recipe documents and we want to expose the average rating value.
190
+
191
+
So all we need is to decorate the method with `@FieldResolver()` and the method parameter with `@Root()` decorator with the root value type of `Recipe` - as simple as that!
193
192
194
193
```ts
195
194
classRecipeResolver {
@@ -200,31 +199,29 @@ class RecipeResolver {
200
199
}
201
200
}
202
201
```
203
-
All we need is to decorate the method with `@FieldResolver()` and the method parameter with `@Root()` decorator with the root value type of `Recipe` - as simple as that!
204
202
205
203
The whole `RecipeResolver` we discussed above with sample implementation of methods looks like this:
0 commit comments