Skip to content

Commit 1f87ebe

Browse files
committed
Update readme info
1 parent 10d5850 commit 1f87ebe

File tree

1 file changed

+55
-47
lines changed

1 file changed

+55
-47
lines changed

README.md

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,20 @@
33
# TypeGraphQL
44
Create GraphQL resolvers and schemas with TypeScript!
55

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.
99

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!
1112

13+
## Getting started
1214
Let's start at the begining with an example.
13-
1415
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-
1916
At first we will create the `Recipe` type, which is the foundations of our API:
2017

2118
```ts
22-
@GraphQLType()
19+
@GraphQLObjectType()
2320
class Recipe {
2421
@Field(type => ID)
2522
readonly id: string;
@@ -39,9 +36,9 @@ class Recipe {
3936
```
4037
Take a look at the decorators:
4138

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`
4542
- 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
4643

4744
This will generate GraphQL type corresponding to this:
@@ -55,10 +52,10 @@ type Recipe {
5552
}
5653
```
5754

58-
Then, we need to define what is the `Rate` type:
55+
Next, we need to define what is the `Rate` type:
5956

6057
```ts
61-
@GraphQLType()
58+
@GraphQLObjectType()
6259
class Rate {
6360
@Field(type => Int)
6461
value: number;
@@ -70,39 +67,39 @@ class Rate {
7067
user: User;
7168
}
7269
```
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).
7471

7572
-----
7673

7774
So, as we have the base of our recipe related types, let's create a resolver!
7875

7976
We will start by creating a class with apropiate decorator:
8077
```ts
81-
@GraphQLResolver(Recipe)
82-
export class RecipeResolver implements Resolver<Recipe> {
78+
@GraphQLResolver(objectType => Recipe)
79+
export class RecipeResolver {
8380
// we will implement this later
8481
}
8582
```
86-
`@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).
8784

8885
Now let's create our first query:
8986
```ts
90-
@GraphQLResolver(Recipe)
91-
export class RecipeResolver implements Resolver<Recipe> {
87+
@GraphQLResolver(objectType => Recipe)
88+
export class RecipeResolver {
9289
constructor(
9390
// declare to inject instance of our repository
9491
private readonly recipeRepository: Repository<Recipe>,
9592
){}
9693

9794
@Query(returnType => Recipe, { nullable: true })
98-
async recipe(@Arguments() { recipeId }: FindRecipeArgs) {
95+
async recipe(@Args() { recipeId }: FindRecipeArgs): Promise<Recipe | undefined> {
9996
return this.recipeRepository.findOneById(recipeId);
10097
}
10198
```
10299
- 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
103100
- `@Query` decorator marks the class method as the query (who would have thought?)
104101
- 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
106103
107104
So, how the `FindRecipeArgs` looks like?
108105
```ts
@@ -121,17 +118,19 @@ type Query {
121118
```
122119
It is great, isn't it? :smiley:
123120
124-
Ok, let's add another one query:
121+
Ok, let's add another query:
125122
```ts
126123
class RecipeResolver {
127124
// ...
128-
@Query(Recipe, { array: true })
125+
@Query(() => Recipe, { array: true })
129126
recipes(): Promise<Array<Recipe>> {
130127
return this.recipeRepository.find();
131128
}
132129
}
133130
```
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>>`.
135134
136135
So now we have two queries in our schema:
137136
```graphql
@@ -145,21 +144,19 @@ Now let's move to the mutations:
145144
```ts
146145
class RecipeResolver {
147146
// ...
148-
@Mutation(Recipe)
149-
@Authorized()
147+
@Mutation(returnType => Recipe)
150148
async rate(
151-
@Argument("rate") rateInput: RateInput,
149+
@Arg("rate") rateInput: RateInput,
152150
@Context() { user }: Context,
153151
) {
154152
// implementation...
155153
}
156154
}
157155
```
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
161158
- 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
163160
164161
Here's how `RateInput` type looks:
165162
```ts
@@ -172,7 +169,7 @@ class RateInput {
172169
value: number;
173170
}
174171
```
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`
176173
177174
The corresponding GraphQL schema:
178175
```graphql
@@ -189,7 +186,9 @@ type Mutation {
189186
}
190187
```
191188
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!
193192
194193
```ts
195194
class RecipeResolver {
@@ -200,31 +199,29 @@ class RecipeResolver {
200199
}
201200
}
202201
```
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!
204202
205203
The whole `RecipeResolver` we discussed above with sample implementation of methods looks like this:
206204
```ts
207-
@GraphQLResolver(Recipe)
208-
export class RecipeResolver implements Resolver<Recipe> {
205+
@GraphQLResolver(objectType => Recipe)
206+
export class RecipeResolver {
209207
constructor(
210208
// inject the repository (or other services)
211209
private readonly recipeRepository: Repository<Recipe>,
212210
){}
213211

214212
@Query(returnType => Recipe, { nullable: true })
215-
recipe(@Arguments() { recipeId }: FindRecipeParams) {
213+
recipe(@Args() { recipeId }: FindRecipeParams) {
216214
return this.recipeRepository.findOneById(recipeId);
217215
}
218216

219-
@Query(Recipe, { array: true })
217+
@Query(() => Recipe, { array: true })
220218
recipes(): Promise<Array<Recipe>> {
221219
return this.recipeRepository.find();
222220
}
223221

224-
@Authorized()
225222
@Mutation(Recipe)
226223
async rate(
227-
@Argument("rate") rateInput: RateInput,
224+
@Arg("rate") rateInput: RateInput,
228225
@Context() { user }: Context,
229226
) {
230227
// find the document
@@ -257,13 +254,13 @@ export class RecipeResolver implements Resolver<Recipe> {
257254
```
258255
259256
As I mentioned, in real life we want to reuse as much TypeScript definition as we can.
260-
So the GQL types would be also used by ORM and the inputs/params would be validated:
257+
So the GQL type classes would be also reused by ORM and the inputs/params could be validated:
261258
262259
```ts
263260
import { Entity, ObjectIdColumn, Column, OneToMany, CreateDateColumn } from "typeorm";
264261

265262
@Entity()
266-
@GraphQLType()
263+
@GraphQLObjectType()
267264
export class Recipe {
268265
@ObjectIdColumn()
269266
@Field(type => ID)
@@ -282,7 +279,7 @@ export class Recipe {
282279
ratings: Rate[];
283280

284281
// note that this field is not stored in DB
285-
@Field(type => Float)
282+
@Field()
286283
averageRating: number;
287284

288285
// and this one is not exposed by GraphQL
@@ -307,6 +304,17 @@ class RateInput {
307304
}
308305
```
309306
310-
Of course TypeGraphQL will validate the input and params with `class-validator` for you too!
307+
Of course TypeGraphQL will validate the input and params with `class-validator` for you too! (in near future :wink:)
308+
309+
## Work in progress
310+
311+
Currently released version is an early alpha. However it's working quite well, so please feel free to test it and experiment with it.
312+
313+
More feedback = less bugs thanks to you! :smiley:
314+
315+
Also, you can find more examples of usage in `tests` folder - there are things like simple field resolvers and many more!
316+
317+
## Roadmap
318+
You can keep track of [development's progress on project board](https://github.com/19majkel94/type-graphql/projects/1).
311319
312-
Stay tuned, come later for more! :wink:
320+
Stay tuned and come back later for more! :wink:

0 commit comments

Comments
 (0)