|
| 1 | +:toc: macro |
| 2 | + |
| 3 | +ifdef::env-github[] |
| 4 | +:tip-caption: :bulb: |
| 5 | +:note-caption: :information_source: |
| 6 | +:important-caption: :heavy_exclamation_mark: |
| 7 | +:caution-caption: :fire: |
| 8 | +:warning-caption: :warning: |
| 9 | +endif::[] |
| 10 | + |
| 11 | +toc::[] |
| 12 | +:idprefix: |
| 13 | +:idseparator: - |
| 14 | +:reproducible: |
| 15 | +:source-highlighter: rouge |
| 16 | +:listing-caption: Listing |
| 17 | + |
| 18 | += GraphQL on Devon4Node |
| 19 | + |
| 20 | +GraphQL is a query language that gets exactly the data that we ask for instead of static predefined responses. |
| 21 | + |
| 22 | +For example, on a regular API a get by id method would return something like: |
| 23 | + |
| 24 | +[source, json] |
| 25 | +---- |
| 26 | +{ |
| 27 | + "location": { |
| 28 | + "lon": 00.14, |
| 29 | + "lat": 54.11 |
| 30 | + }, |
| 31 | + "station": "dsrEE3Sg", |
| 32 | + "visibility": 5000, |
| 33 | + "wind":{ |
| 34 | + "speed": 6.2, |
| 35 | + "deg": 78 |
| 36 | + }, |
| 37 | + "logs": [...] |
| 38 | + ... |
| 39 | +} |
| 40 | +---- |
| 41 | +But if we want to get *only* the wind data we have to create another endpoint that returns the specified data. |
| 42 | + |
| 43 | +But instead with graphQL we can get different information without creating new endpoints, in this case we only want the wind data so it would return: |
| 44 | + |
| 45 | +[source, json] |
| 46 | +---- |
| 47 | +{ |
| 48 | + "wind":{ |
| 49 | + "speed": 6.2, |
| 50 | + "deg": 78 |
| 51 | + } |
| 52 | +} |
| 53 | +---- |
| 54 | + |
| 55 | +To install it: |
| 56 | + |
| 57 | +[source,bash] |
| 58 | +---- |
| 59 | +yarn add @nestjs/graphql graphql-tools graphql apollo-server-express |
| 60 | +---- |
| 61 | + |
| 62 | +== Schema first |
| 63 | + |
| 64 | +[NOTE] |
| 65 | +==== |
| 66 | +This tutorial uses the schema first method. |
| 67 | +
|
| 68 | +We assume you have already a functioning TODO module / app. |
| 69 | +
|
| 70 | +If not you can use https://github.com/devonfw/devon4node/tree/develop/samples/graphql[Devon4node GraphQL sample] |
| 71 | +==== |
| 72 | + |
| 73 | +First we need to import GraphQLModule to our `app.module.ts`. |
| 74 | + |
| 75 | +[source,typescript] |
| 76 | +---- |
| 77 | +... |
| 78 | +import { GraphQLModule } from '@nestjs/graphql'; |
| 79 | +import { join } from 'path'; |
| 80 | +
|
| 81 | +@Module({ |
| 82 | + imports: [ |
| 83 | + // Your module import |
| 84 | + GraphQLModule.forRoot({ |
| 85 | + typePaths: ['./**/*.graphql'], |
| 86 | + definitions: { |
| 87 | + path: join(process.cwd(), 'src/graphql.ts'), |
| 88 | + outputAs: 'class', |
| 89 | + }, |
| 90 | + }), |
| 91 | + ], |
| 92 | +}) |
| 93 | +export class AppModule {} |
| 94 | +---- |
| 95 | + |
| 96 | +The `typePaths` indicates the location of the schema definition files. |
| 97 | + |
| 98 | +The `definitions` indicates the file where the typescript definitions will automatically save, adding the `outputAs: 'class'` saves those definitions as classes. |
| 99 | + |
| 100 | +=== Schema |
| 101 | + |
| 102 | +Graphql is a typed language with `object types`, `scalars`, and `enums`. |
| 103 | + |
| 104 | +We use `querys` to define the methods we are going to use for fetching data, and `mutations` are used for modifying this data, similar to how `GET` and `POST` work. |
| 105 | + |
| 106 | +Let's define the elements, querys and mutations that our module is going to have. |
| 107 | + |
| 108 | +For that we have to create a graphql file on our module, on this case we are going to name it "schema.graphql". |
| 109 | + |
| 110 | +[source,typescript] |
| 111 | +---- |
| 112 | +type Todo { |
| 113 | + id: ID |
| 114 | + task: String |
| 115 | +} |
| 116 | +
|
| 117 | +type Query { |
| 118 | + todos: [Todo] |
| 119 | + todoById: Todo |
| 120 | +} |
| 121 | +
|
| 122 | +type Mutation { |
| 123 | + createTodo(task: String): Todo |
| 124 | + deleteTodo(id: String): Todo |
| 125 | +} |
| 126 | +---- |
| 127 | + |
| 128 | +For more information about Types go to the official https://graphql.org/learn/schema/[graphQL documentation] |
| 129 | + |
| 130 | + |
| 131 | +=== Resolver |
| 132 | + |
| 133 | +Resolvers has the instructions to turn GraphQL orders into the data requested. |
| 134 | + |
| 135 | +To create a resolver we go to our module and then create a new `todo.resolver.ts` file, import the decorators needed and set the resolver. |
| 136 | + |
| 137 | +[source,typescript] |
| 138 | +---- |
| 139 | +import { Resolver, Args, Mutation, Query } from '@nestjs/graphql'; |
| 140 | +import { TodoService } from '../services/todo.service'; |
| 141 | +import { Todo } from '../schemas/todo.schema'; |
| 142 | +
|
| 143 | +@Resolver() |
| 144 | +export class TodoResolver { |
| 145 | + constructor(private readonly todoService: TodoService) {} |
| 146 | +
|
| 147 | + @Query('todos') |
| 148 | + findAll(): Promise<Todo[]> { |
| 149 | + return this.todoService.findAll(); |
| 150 | + } |
| 151 | +
|
| 152 | + @Query('todoById') |
| 153 | + findOneById(@Args('id') id: string): Promise<Todo | null> { |
| 154 | + return this.todoService.findOneById(id); |
| 155 | + } |
| 156 | +
|
| 157 | + @Mutation() |
| 158 | + createTodo(@Args('task') task: string): Promise<Todo> { |
| 159 | + return this.todoService.create(task); |
| 160 | + } |
| 161 | +
|
| 162 | + @Mutation() |
| 163 | + deleteTodo(@Args('id') id: string): Promise<Todo | null> { |
| 164 | + return this.todoService.delete(id); |
| 165 | + } |
| 166 | +} |
| 167 | +---- |
| 168 | + |
| 169 | +`@Resolver()` indicates that the next class is a resolver. |
| 170 | + |
| 171 | +`@Query` is used to get data. |
| 172 | + |
| 173 | +`@Mutation` is used to create or modify data. |
| 174 | + |
| 175 | +Here we have also an argument decorator `@Args` which is an object with the arguments passed into the field in the query. |
| 176 | + |
| 177 | +By default we can access the query or mutation using the method's name, for example: |
| 178 | + |
| 179 | +For the `deleteTodo` mutation. |
| 180 | + |
| 181 | +[source,typescript] |
| 182 | +---- |
| 183 | +mutation { |
| 184 | + deleteTodo( id: "6f7ed2q8" ){ |
| 185 | + id, |
| 186 | + task |
| 187 | + } |
| 188 | +} |
| 189 | +---- |
| 190 | + |
| 191 | +But if we write something different on the decorator, we change the name, for example: |
| 192 | + |
| 193 | +For the `findAll` query, we named it `todos`. |
| 194 | +[source,typescript] |
| 195 | +---- |
| 196 | +{ |
| 197 | + todos{ |
| 198 | + id, |
| 199 | + task |
| 200 | + } |
| 201 | +} |
| 202 | +---- |
| 203 | +Also if we go back to the `schema.graphql`, we will see how we define the query with `todos`. |
| 204 | + |
| 205 | +Learn more about resolvers, mutations and their argument decorators on the https://docs.nestjs.com/graphql/resolvers#schema-first[NestJS documentation]. |
| 206 | + |
| 207 | + |
| 208 | +=== Playground |
| 209 | + |
| 210 | +To test our backend we can use tools as Postman, but graphql already gives us a playground to test our resolvers, we can access by default on `http://localhost:3000/graphql`. |
| 211 | + |
| 212 | +We can call a query, or several querys this way: |
| 213 | + |
| 214 | +[source,typescript] |
| 215 | +---- |
| 216 | +{ |
| 217 | + findAll{ |
| 218 | + id, |
| 219 | + task |
| 220 | + } |
| 221 | +} |
| 222 | +---- |
| 223 | + |
| 224 | +And the output will look something like: |
| 225 | +[source,typescript] |
| 226 | +---- |
| 227 | +{ |
| 228 | + "data": { |
| 229 | + "findAll": [ |
| 230 | + { |
| 231 | + "id": "5fb54b30e686cb49500b6728", |
| 232 | + "task": "clean dishes" |
| 233 | + }, |
| 234 | + { |
| 235 | + "id": "5fb54b3be686cb49500b672a", |
| 236 | + "task": "burn house" |
| 237 | + } |
| 238 | + ] |
| 239 | + } |
| 240 | +} |
| 241 | +---- |
| 242 | + |
| 243 | +As we can see, we get a json "data" with an array of results. |
| 244 | + |
| 245 | +And for our mutations it's very similar, in this case we create a todo with task "rebuild house" and we are going to ask on the response just for the task data, we don't want the id. |
| 246 | + |
| 247 | +[source,typescript] |
| 248 | +---- |
| 249 | +mutation{ |
| 250 | + createTodo ( |
| 251 | + task: "rebuild house" |
| 252 | + ){ |
| 253 | + task |
| 254 | + } |
| 255 | +} |
| 256 | +---- |
| 257 | + |
| 258 | +And the output |
| 259 | + |
| 260 | +[source,json] |
| 261 | +---- |
| 262 | +{ |
| 263 | + "data": { |
| 264 | + "createTodo": { |
| 265 | + "task": "rebuild house" |
| 266 | + } |
| 267 | + } |
| 268 | +} |
| 269 | +---- |
| 270 | + |
| 271 | +In this case we return just one item so there is no array, we also got just the `task data` but if we want the `id` too, we just have to add it on the request. |
| 272 | + |
| 273 | +To make the playground unavailable we can add an option to the app.module import: |
| 274 | + |
| 275 | +[source,typescript] |
| 276 | +---- |
| 277 | +... |
| 278 | +GraphQLModule.forRoot({ |
| 279 | + ... |
| 280 | + playground: false, |
| 281 | +}), |
| 282 | +... |
| 283 | +---- |
| 284 | + |
| 285 | +For further information go to the official https://docs.nestjs.com/graphql/quick-start[NestJS documentation] |
0 commit comments