Skip to content

Commit a2bd592

Browse files
committed
tweaks 8e
1 parent 117bd72 commit a2bd592

File tree

10 files changed

+378
-231
lines changed

10 files changed

+378
-231
lines changed

src/content/0/en/part0a.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Participants are expected to have good programming skills, basic knowledge of we
1717

1818
Previous knowledge of JavaScript or other course topics is not required.
1919

20+
How much programming experience is needed? It is hard to say, but you should be pretty fluent with <i>your</i> language. This level of fluency takes usually at least 100-200 of hours practice to develop.
21+
2022
### Course material
2123

2224
The course material is meant to be read one part at a time and in order.

src/content/8/en/part8b.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,9 @@ const PersonForm = ({ setError }) => {
602602
refetchQueries: [ {query: ALL_PERSONS } ],
603603
// highlight-start
604604
onError: (error) => {
605-
setError(error.graphQLErrors[0].message)
605+
const errors = error.graphQLErrors[0].extensions.error.errors
606+
const messages = Object.values(errors).map(e => e.message).join('\n')
607+
setError(messages)
606608
}
607609
// highlight-end
608610
})
@@ -611,7 +613,8 @@ const PersonForm = ({ setError }) => {
611613
}
612614
```
613615
614-
<!-- Renderöidään mahdollinen virheilmoitus näytölle -->
616+
We have to dig quite deep to the error object until we find the proper error messages...
617+
615618
We can then render the error message on the screen as necessary:
616619
617620
```js

src/content/8/en/part8c.md

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ Every user is connected to a bunch of other persons in the system through the _f
237237

238238
Logging in and identifying the user are handled the same way we used in [part 4](/en/part4/token_authentication) when we used REST, by using tokens.
239239

240-
241240
Let's extend the schema like so:
242241

243242
```js
@@ -276,39 +275,44 @@ The resolvers of the mutations are as follows:
276275
```js
277276
const jwt = require('jsonwebtoken')
278277

279-
const JWT_SECRET = 'NEED_HERE_A_SECRET_KEY'
280-
281278
Mutation: {
282279
// ..
283280
createUser: async (root, args) => {
284281
const user = new User({ username: args.username })
285282

286283
return user.save()
287284
.catch(error => {
288-
throw new UserInputError(error.message, {
289-
invalidArgs: args,
285+
throw new GraphQLError('Creating the user failed', {
286+
extensions: {
287+
code: 'BAD_USER_INPUT',
288+
invalidArgs: args.name,
289+
error
290+
}
290291
})
291292
})
292293
},
293294
login: async (root, args) => {
294295
const user = await User.findOne({ username: args.username })
295296

296297
if ( !user || args.password !== 'secret' ) {
297-
throw new UserInputError("wrong credentials")
298+
throw new GraphQLError('wrong credentials', {
299+
extensions: {
300+
code: 'BAD_USER_INPUT'
301+
}
302+
})
298303
}
299304

300305
const userForToken = {
301306
username: user.username,
302307
id: user._id,
303308
}
304309

305-
return { value: jwt.sign(userForToken, JWT_SECRET) }
310+
return { value: jwt.sign(userForToken, process.env.JWT_SECRET) }
306311
},
307312
},
308313
```
309314

310-
The new user mutation is straightforward. The login mutation checks if the username/password pair is valid. And if it is indeed valid, it returns a jwt token familiar from [part 4](/en/part4/token_authentication).
311-
315+
The new user mutation is straightforward. The login mutation checks if the username/password pair is valid. And if it is indeed valid, it returns a jwt token familiar from [part 4](/en/part4/token_authentication). Note that the *JWT\_SECRET* must be defined in the <i>.env</i> file.
312316

313317
User creation is done now as follows:
314318

@@ -342,25 +346,26 @@ In the Apollo Explorer, the header is added to a query like so:
342346

343347
![](../../images/8/24x.png)
344348

345-
Let's now expand the definition of the _server_ object by adding a third parameter [context](https://www.apollographql.com/docs/apollo-server/data/data/#context-argument) to the constructor call:
349+
Modify the startup of the backend by giving the function that handles the startup [startStandaloneServer](https://www.apollographql.com/docs/apollo-server/api/standalone/) another parameter [context](https://www.apollographql.com /docs/apollo-server/data/context/)
346350

347351
```js
348-
const server = new ApolloServer({
349-
typeDefs,
350-
resolvers,
352+
startStandaloneServer(server, {
353+
listen: { port: 4000 },
351354
// highlight-start
352-
context: async ({ req }) => {
355+
context: async ({ req, res }) => {
353356
const auth = req ? req.headers.authorization : null
354-
if (auth && auth.toLowerCase().startsWith('bearer ')) {
357+
if (auth && auth.startsWith('Bearer ')) {
355358
const decodedToken = jwt.verify(
356-
auth.substring(7), JWT_SECRET
359+
auth.substring(7), process.env.JWT_SECRET
357360
)
358-
359-
const currentUser = await User.findById(decodedToken.id).populate('friends')
361+
const currentUser = await User
362+
.findById(decodedToken.id).populate('friends')
360363
return { currentUser }
361364
}
362-
}
365+
},
363366
// highlight-end
367+
}).then(({ url }) => {
368+
console.log(`Server ready at ${url}`)
364369
})
365370
```
366371

@@ -378,6 +383,9 @@ Query: {
378383
}
379384
},
380385
```
386+
If the header has the correct value, the query returns the user information identified by the header
387+
388+
![](../../images/8/50new.png)
381389

382390
### Friends list
383391

@@ -389,33 +397,41 @@ _addPerson_ mutation changes like so:
389397

390398
```js
391399
Mutation: {
392-
addPerson: async (root, args, context) => { // highlight-line
393-
const person = new Person({ ...args })
394-
// highlight-start
395-
const currentUser = context.currentUser
396-
397-
if (!currentUser) {
398-
throw new AuthenticationError("not authenticated")
399-
}
400-
// highlight-end
400+
addPerson: async (root, args, context) => { // highlight-line
401+
const person = new Person({ ...args })
402+
const currentUser = context.currentUser // highlight-line
401403

402-
try {
403-
await person.save()
404-
currentUser.friends = currentUser.friends.concat(person) // highlight-line
405-
await currentUser.save() // highlight-line
406-
} catch (error) {
407-
throw new UserInputError(error.message, {
408-
invalidArgs: args,
409-
})
410-
}
404+
// highlight-start
405+
if (!currentUser) {
406+
throw new GraphQLError('not authenticated', {
407+
extensions: {
408+
code: 'BAD_USER_INPUT',
409+
}
410+
})
411+
}
412+
// highlight-end
411413

412-
return person
413-
},
414+
try {
415+
await person.save()
416+
currentUser.friends = currentUser.friends.concat(person) // highlight-line
417+
await currentUser.save() // highlight-line
418+
} catch (error) {
419+
throw new GraphQLError('Saving user failed', {
420+
extensions: {
421+
code: 'BAD_USER_INPUT',
422+
invalidArgs: args.name,
423+
error
424+
}
425+
})
426+
}
427+
428+
return person
429+
},
414430
//...
415431
}
416432
```
417433

418-
If a logged-in user cannot be found from the context, an _AuthenticationError_ is thrown. Creating new persons is now done with _async/await_ syntax, because if the operation is successful, the created person is added to the friends list of the user.
434+
If a logged-in user cannot be found from the context, an _GraphQLError_ with a proper message is thrown. Creating new persons is now done with _async/await_ syntax, because if the operation is successful, the created person is added to the friends list of the user.
419435

420436
Let's also add functionality for adding an existing user to your friends list. The mutation is as follows:
421437

@@ -436,7 +452,9 @@ And the mutation's resolver:
436452
currentUser.friends.map(f => f._id.toString()).includes(person._id.toString())
437453

438454
if (!currentUser) {
439-
throw new AuthenticationError("not authenticated")
455+
throw new GraphQLError('wrong credentials', {
456+
extensions: { code: 'BAD_USER_INPUT' }
457+
})
440458
}
441459

442460
const person = await Person.findOne({ name: args.name })

src/content/8/en/part8d.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,12 @@ const App = () => {
159159
}
160160
```
161161

162-
The current code of the application can be found on [Github](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-6), branch <i>part8-6</i>.
163-
164162
### Adding a token to a header
165163

166164
After the backend changes, creating new persons requires that a valid user token is sent with the request. In order to send the token, we have to change the way we define the _ApolloClient_ object in <i>index.js</i> a little.
167165

168166
```js
167+
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '@apollo/client' // highlight-line
169168
import { setContext } from '@apollo/client/link/context' // highlight-line
170169

171170
// highlight-start
@@ -174,21 +173,23 @@ const authLink = setContext((_, { headers }) => {
174173
return {
175174
headers: {
176175
...headers,
177-
authorization: token ? `bearer ${token}` : null,
176+
authorization: token ? `Bearer ${token}` : null,
178177
}
179178
}
180179
})
181180
// highlight-end
182181

183-
const httpLink = new HttpLink({ uri: 'http://localhost:4000' }) // highlight-line
182+
const httpLink = createHttpLink({
183+
uri: '/graphql',
184+
})
184185

185186
const client = new ApolloClient({
186187
cache: new InMemoryCache(),
187188
link: authLink.concat(httpLink) // highlight-line
188189
})
189190
```
190191

191-
The link parameter given to the _client_ object defines how apollo connects to the server. Here, the normal [httpLink](https://www.apollographql.com/docs/link/links/http.htm) connection is modified so that the request's <i>authorization</i> [header](https://www.apollographql.com/docs/react/networking/authentication/#header) contains the token if one has been saved to the localStorage.
192+
The field _uri_ that was previously used when creating the _client_ object has been replaced by the field _link_, which defines in a more complicated case how Apollo is connected to the server. The server url is now wrapped using the function [createHttpLink](https://www.apollographql.com/docs/link/links/http.htm) into a suitable httpLink object. The link is modified by the [context](https://www .apollographql.com/docs/react/api/link/apollo-link-context/#overview) defined by the authLink object so that a possible token in localStorage is [set to header](https://www.apollographql.com/docs/react/networking/authentication /#header) <i>authorization</i> for each request to the server.
192193

193194
Creating new persons and changing numbers works again. There is however one remaining problem. If we try to add a person without a phone number, it is not possible.
194195

@@ -217,7 +218,7 @@ const PersonForm = ({ setError }) => {
217218
}
218219
```
219220

220-
Current application code can be found on [Github](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-7), branch <i>part8-7</i>.
221+
Current application code can be found on [Github](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-6), branch <i>part8-6</i>.
221222

222223
### Updating cache, revisited
223224

@@ -231,14 +232,16 @@ const PersonForm = ({ setError }) => {
231232
const [ createPerson ] = useMutation(CREATE_PERSON, {
232233
refetchQueries: [ {query: ALL_PERSONS} ], // highlight-line
233234
onError: (error) => {
234-
setError(error.graphQLErrors[0].message)
235+
const errors = error.graphQLErrors[0].extensions.error.errors
236+
const messages = Object.values(errors).map(e => e.message).join('\n')
237+
setError(messages)
235238
}
236239
})
237240
```
238241
239242
This approach is pretty good, the drawback being that the query is always rerun with any updates.
240243
241-
It is possible to optimize the solution by handling updating the cache ourselves. This is done by defining a suitable [update](https://www.apollographql.com/docs/react/data/mutations/#update) callback for the mutation, which Apollo runs after the mutation:
244+
It is possible to optimize the solution by handling updating the cache ourselves. This is done by defining a suitable [update](https://www.apollographql.com/docs/react/data/mutations/#the-update-function) callback for the mutation, which Apollo runs after the mutation:
242245
243246
```js
244247
const PersonForm = ({ setError }) => {
@@ -276,7 +279,7 @@ Be diligent with the cache. Old data in cache can cause hard-to-find bugs. As we
276279
277280
> <i>There are only two hard things in Computer Science: cache invalidation and naming things.</i> Read more [here](https://martinfowler.com/bliki/TwoHardThings.html).
278281
279-
The current code of the application can be found on [Github](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-8), branch <i>part8-8</i>.
282+
The current code of the application can be found on [Github](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-7), branch <i>part8-7</i>.
280283
281284
</div>
282285

0 commit comments

Comments
 (0)