Skip to content

Commit 1785438

Browse files
authored
Inline versioned tutorial (#1762)
* begin with v3 tutorial * inline versioning * no tutorial in docs * error handling * filtering and pagination * point to v3 * micro adjustments * drop empty code block * use native details tag
1 parent 2d73fc5 commit 1785438

File tree

6 files changed

+250
-8
lines changed

6 files changed

+250
-8
lines changed

website/src/pages/tutorial/basic/03-graphql-server.mdx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ You are going to use the HTTP protocol to serve the GraphQL server, but note tha
88

99
## Creating a GraphQL HTTP Server with Yoga
1010

11-
In this tutorial, we will be using `@graphql-yoga/node` for building our Node.js GraphQL HTTP server.
11+
In this tutorial, we will be using GraphQL Yoga for building our Node.js GraphQL HTTP server.
1212

1313
<Callout>
1414
Yoga can also run on other platforms, such as Cloudflare Workers or Deno. We
1515
recommend checking out [the integrations
16-
section](/docs/integrations/integration-with-cloudflare-workers) in our
16+
section](/v3/integrations/integration-with-cloudflare-workers) in our
1717
documentation after finishing the tutorial.
1818
</Callout>
1919

20+
<details>
21+
<summary>v2</summary>
22+
2023
You'll need the `@graphql-yoga/node` package available in your project, so install it using the following command:
2124

2225
<PackageCmd packages={['--save-exact @graphql-yoga/node']} />
@@ -46,7 +49,35 @@ $ cross-env NODE_ENV=development ts-node-dev --exit-child --respawn src/main.ts
4649
[2022-02-25 14:40:17.818 +0000] INFO (13296 on DESKTOP-U72CGKK): GraphQL Server running at http://127.0.0.1:4000/graphql.
4750
```
4851

49-
Now open your browser and navigate to `http://localhost:4000/graphql`.
52+
</details>
53+
54+
<details open>
55+
<summary>v3</summary>
56+
57+
You'll need the `graphql-yoga` package available in your project, so install it using
58+
the following command:
59+
60+
<PackageCmd packages={['--save-exact graphql-yoga']} />
61+
62+
Now, update `src/main.ts` to create a simple GraphQL HTTP server on port `4000`:
63+
64+
```ts filename="src/main.ts"
65+
import { createYoga } from 'graphql-yoga'
66+
import { createServer } from 'http'
67+
import { schema } from './schema'
68+
69+
function main() {
70+
const yoga = createYoga({ schema })
71+
const server = createServer(yoga)
72+
server.listen(4000)
73+
}
74+
75+
main()
76+
```
77+
78+
Now, try to run your server again with `npm run dev` (or, `npm run start`), open your browser and navigate to `http://localhost:4000/graphql`.
79+
80+
</details>
5081

5182
Type in the following operation in the left editor section and press the `Play` button for executing the operation against the GraphQL server.
5283

website/src/pages/tutorial/basic/07-connecting-server-and-database.mdx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ export async function createContext(): Promise<GraphQLContext> {
3232

3333
Now, you'll need to import the `createContext` function and make sure you add it as part of the GraphQL execution process:
3434

35+
<details>
36+
<summary>v2</summary>
37+
3538
```typescript
36-
// ... other imports ...
39+
import { createServer } from '@graphql-yoga/node'
40+
import { schema } from './schema'
3741
import { createContext } from './context'
3842

3943
async function main() {
@@ -44,6 +48,28 @@ async function main() {
4448
main()
4549
```
4650

51+
</details>
52+
53+
<details open>
54+
<summary>v3</summary>
55+
56+
```typescript
57+
import { createYoga } from 'graphql-yoga'
58+
import { createServer } from 'http'
59+
import { schema } from './schema'
60+
import { createContext } from './context'
61+
62+
function main() {
63+
const yoga = createYoga({ schema, context: createContext })
64+
const server = createServer(yoga)
65+
server.listen(4000)
66+
}
67+
68+
main()
69+
```
70+
71+
</details>
72+
4773
In the next step, you'll connect the Prisma client and your GraphQL server!
4874

4975
## Updating the Resolver Functions to Use Prisma Client

website/src/pages/tutorial/basic/09-error-handling.mdx

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ This is great, but now we never know why our request failed 🤔 and the error m
121121

122122
Let's change the implementation to expose the much more helpful message `Cannot post comment on non-existing link with id 'X'.` instead of `Unexpected error.`.
123123

124+
<details>
125+
<summary>v2</summary>
126+
124127
For doing so you will use the `GraphQLYogaError` class that is exported from the `@graphql-yoga/node` package.
125128

126129
Add the following catch handler for mapping a foreign key error into a `GraphQLYogaError`:
@@ -169,6 +172,61 @@ const resolvers = {
169172
The error code `P2003` indicates a foreign key constraint error. You can learn more about it in [the Prisma documentation](https://prisma.io/docs/reference/api-reference/error-reference#p2003).
170173
You use that code for identifying this edge case of a missing link and then throw a `GraphQLYogaError` with a useful error message instead.
171174

175+
</details>
176+
177+
<details open>
178+
<summary>v3</summary>
179+
180+
For doing so you will use the `GraphQLError` class that is exported from the `graphql` package.
181+
182+
Add the following catch handler for mapping a foreign key error into a `GraphQLError`:
183+
184+
```ts
185+
// ... other imports ...
186+
import { GraphQLError } from 'graphql'
187+
// ... other imports ...
188+
189+
const resolvers = {
190+
// ... other resolver maps ...
191+
Mutation: {
192+
// ... other Mutation object type field resolver functions ...
193+
async postCommentOnLink(
194+
parent: unknown,
195+
args: { linkId: string; body: string },
196+
context: GraphQLContext,
197+
) {
198+
const comment = await context.prisma.comment
199+
.create({
200+
data: {
201+
body: args.body,
202+
linkId: parseInt(args.linkId),
203+
},
204+
})
205+
.catch((err: unknown) => {
206+
if (
207+
err instanceof PrismaClientKnownRequestError &&
208+
err.code === 'P2003'
209+
) {
210+
return Promise.reject(
211+
new GraphQLError(
212+
`Cannot post comment on non-existing link with id '${args.linkId}'.`,
213+
),
214+
)
215+
}
216+
return Promise.reject(err)
217+
})
218+
219+
return comment
220+
},
221+
},
222+
}
223+
```
224+
225+
The error code `P2003` indicates a foreign key constraint error. You can learn more about it in [the Prisma documentation](https://prisma.io/docs/reference/api-reference/error-reference#p2003).
226+
You use that code for identifying this edge case of a missing link and then throw a `GraphQLError` with a useful error message instead.
227+
228+
</details>
229+
172230
Restart the server using `npm run dev` and again execute the mutation operation using GraphiQL.
173231

174232
```graphql
@@ -200,9 +258,9 @@ You will receive a response with the `Cannot post comment on non-existing link w
200258
}
201259
```
202260

203-
As you might have noticed, GraphQLYoga will exclude `GraphQLYogaError` errors thrown within the resolvers from masking the error masking.
261+
As you might have noticed, GraphQL Yoga will exclude wrapped errors thrown within the resolvers from masking the error masking.
204262

205-
That means every time you want to expose an error to the outside world you should throw a `GraphQLYogaError` error.
263+
That means every time you want to expose an error to the outside world you should throw such an error.
206264

207265
At the same time, any other unexpected error will automatically be masked from the outside world, bringing you sensible and safe defaults for a GraphQL Yoga production deployment!
208266

@@ -306,8 +364,12 @@ There are multiple mathematical numeral systems. The implementation tries to be
306364

307365
Add the following code to the `src/schema.ts` file.
308366

367+
<details>
368+
<summary>v2</summary>
369+
309370
```ts filename="src/schema.ts"
310371
// ... other code ...
372+
import { GraphQLYogaError } from '@graphql-yoga/node'
311373

312374
const parseIntSafe = (value: string): number | null => {
313375
if (/^(\d+)$/.test(value)) {
@@ -359,6 +421,67 @@ const resolvers = {
359421
}
360422
```
361423

424+
</details>
425+
426+
<details open>
427+
<summary>v3</summary>
428+
429+
```ts filename="src/schema.ts"
430+
// ... other code ...
431+
import { GraphQLError } from 'graphql'
432+
433+
const parseIntSafe = (value: string): number | null => {
434+
if (/^(\d+)$/.test(value)) {
435+
return parseInt(value, 10)
436+
}
437+
return null
438+
}
439+
440+
const resolvers = {
441+
// ... other resolver maps ...
442+
Mutation: {
443+
// ... other field resolver functions
444+
async postCommentOnLink(
445+
parent: unknown,
446+
args: { linkId: string; body: string },
447+
context: GraphQLContext,
448+
) {
449+
const linkId = parseIntSafe(args.linkId)
450+
if (linkId === null) {
451+
return Promise.reject(
452+
new GraphQLError(
453+
`Cannot post comment on non-existing link with id '${args.linkId}'.`,
454+
),
455+
)
456+
}
457+
458+
const comment = await context.prisma.comment
459+
.create({
460+
data: {
461+
body: args.body,
462+
linkId,
463+
},
464+
})
465+
.catch((err: unknown) => {
466+
if (err instanceof PrismaClientKnownRequestError) {
467+
if (err.code === 'P2003') {
468+
return Promise.reject(
469+
new GraphQLError(
470+
`Cannot post comment on non-existing link with id '${args.linkId}'.`,
471+
),
472+
)
473+
}
474+
}
475+
return Promise.reject(err)
476+
})
477+
return comment
478+
},
479+
},
480+
}
481+
```
482+
483+
</details>
484+
362485
The regex `/^(\d+)$/` simply verifies that every character within the `value` is a digit. In case the value includes a non-digit character, you simply return `null` and then reject the resolver with a `GraphQLYogaError` error.
363486

364487
Returning `null` instead of returning `NaN` is the better choice here, as it allows nice TypeScript checks (`linkId === null`).

website/src/pages/tutorial/basic/10-filtering-and-pagination.mdx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ Let's use the value 30 as the default value and 50 as the upper limit and 1 as t
178178

179179
Adjust the current schema resolver implementation according to the following.
180180

181+
<details>
182+
<summary>v2</summary>
183+
181184
```ts
185+
import { GraphQLYogaError } from '@graphql-yoga/node'
182186
// ... other code ...
183187

184188
const applyTakeConstraints = (params: {
@@ -228,6 +232,64 @@ const resolvers = {
228232
}
229233
```
230234

235+
</details>
236+
237+
<details open>
238+
<summary>v3</summary>
239+
240+
```ts
241+
// ... other code ...
242+
import { GraphQLError } from 'graphql'
243+
244+
const applyTakeConstraints = (params: {
245+
min: number
246+
max: number
247+
value: number
248+
}) => {
249+
if (params.value < params.min || params.value > params.max) {
250+
throw new GraphQLError(
251+
`'take' argument value '${params.value}' is outside the valid range of '${params.min}' to '${params.max}'.`,
252+
)
253+
}
254+
return params.value
255+
}
256+
257+
const resolvers = {
258+
// ... other resolvers maps ...
259+
Query: {
260+
// ... other Query object type resolver functions ...
261+
async feed(
262+
parent: unknown,
263+
args: { filterNeedle?: string; skip?: number; take?: number },
264+
context: GraphQLContext,
265+
) {
266+
const where = args.filterNeedle
267+
? {
268+
OR: [
269+
{ description: { contains: args.filterNeedle } },
270+
{ url: { contains: args.filterNeedle } },
271+
],
272+
}
273+
: {}
274+
275+
const take = applyTakeConstraints({
276+
min: 1,
277+
max: 50,
278+
value: args.take ?? 30,
279+
})
280+
281+
return context.prisma.link.findMany({
282+
where,
283+
skip: args.skip,
284+
take,
285+
})
286+
},
287+
},
288+
}
289+
```
290+
291+
</details>
292+
231293
Cool! Now we can try executing an operation with a `take` argument value outside the range!
232294

233295
Execute the following query operation via GraphiQL:

website/src/pages/tutorial/basic/11-summary.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
In this tutorial, you learned how to build a simple HackerNews clone GraphQL server from scratch using GraphQL Yoga!
44

5-
You can find the code of full [code of the tutorial on GitHub](https://github.com/dotansimha/graphql-yoga/tree/v2/examples/hackernews).
5+
You can find the code of full [code of the tutorial on GitHub](https://github.com/dotansimha/graphql-yoga/tree/v3/examples/hackernews).
66

77
Congratulations on completing the tutorial! We can't wait to see what you build next.
88

website/src/pages/tutorial/basic/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ You are going to use the following technologies:
2727

2828
<Callout>
2929
You can find the [code of the tutorial in this
30-
repository](https://github.com/dotansimha/graphql-yoga/tree/v2/examples/hackernews).
30+
repository](https://github.com/dotansimha/graphql-yoga/tree/v3/examples/hackernews).
3131
</Callout>
3232

3333
## What to Expect

0 commit comments

Comments
 (0)