Skip to content

Commit eec04ce

Browse files
authored
Merge branch 'fullstack-hy2020:source' into source
2 parents bb8d74d + c563014 commit eec04ce

File tree

16 files changed

+609
-434
lines changed

16 files changed

+609
-434
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/13/fi/osa13b.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ Sovelluksen tämänhetkinen koodi on kokonaisuudessaan [GitHubissa](https://gith
981981
#### Tehtävä 13.13.
982982

983983
Toteuta sovellukseen kaikki blogit palauttavaan reittiin filtteröinti hakusanan perusteella. Filtteröinti toimii seuraavasti
984-
- _GET /api/blogs?serch=react_ palauttaa ne blogit joiden kentässä <i>title</i> esiintyy hakusana <i>react</i>, hakusana on epäcasesensitiivinen
984+
- _GET /api/blogs?search=react_ palauttaa ne blogit joiden kentässä <i>title</i> esiintyy hakusana <i>react</i>, hakusana on epäcasesensitiivinen
985985
- _GET /api/blogs_ palauttaa kaikki blogit
986986

987987

@@ -990,7 +990,7 @@ Toteuta sovellukseen kaikki blogit palauttavaan reittiin filtteröinti hakusanan
990990

991991
Laajenna filtteriä siten, että se etsii hakusanaa kentistä <i>title</i> ja <i>author</i>, eli
992992

993-
_GET /api/blogs?serch=jami_ palauttaa ne blogit joiden kentässä <i>title</i> tai kentässä <i>author</i> esiintyy hakusana <i>jami</i>
993+
_GET /api/blogs?search=jami_ palauttaa ne blogit joiden kentässä <i>title</i> tai kentässä <i>author</i> esiintyy hakusana <i>jami</i>
994994
#### Tehtävä 13.15.
995995

996996
Muokkaa blogien reittiä siten, että se palauttaa blogit tykkäysten perusteella laskevassa järjestyksessä. Etsi [dokumentaatiosta](https://sequelize.org/master/manual/model-querying-basics.html) ohjeet järjestämiselle.

src/content/2/fi/osa2d.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ Sovelluksen tämänhetkinen koodi on kokonaisuudessaan [GitHubissa](https://gith
694694
695695
On taas tehtävien aika. Tehtävien haastavuus alkaa nousta, sillä koodin toimivuuteen vaikuttaa myös se, kommunikoiko React-koodi oikein JSON Serverin kanssa.
696696
697-
Meidän onkin syytä päivittää websovelluskehittäjän vala <i>Full stack -sovelluskehittäjän valaksi</i>, eli muistuttaa itseämme siitä, että frontendin koodin lisäksi seuraamme koko ajan sitä, miten fronend ja backend kommunikoivat.
697+
Meidän onkin syytä päivittää websovelluskehittäjän vala <i>Full stack -sovelluskehittäjän valaksi</i>, eli muistuttaa itseämme siitä, että frontendin koodin lisäksi seuraamme koko ajan sitä, miten frontend ja backend kommunikoivat.
698698
699699
Full stack -ohjelmointi on <i>todella</i> hankalaa, ja sen takia lupaan hyödyntää kaikkia ohjelmointia helpottavia keinoja:
700700

src/content/6/en/part6a.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ lang: en
77

88
<div class="content">
99

10-
So far, we have followed the state management conventions recommended by React. We have placed the state and the functions for handling it in [higher level](https://reactjs.org/docs/lifting-state-up.html) of the component structyre of the application. Quite often most of the app state and state altering functions reside rirectly in the root component. The state and its handler methods have then been passed to other components with props. This works up to a certain point, but when applications grow larger, state management becomes challenging.
10+
So far, we have followed the state management conventions recommended by React. We have placed the state and the functions for handling it in [higher level](https://reactjs.org/docs/lifting-state-up.html) of the component structure of the application. Quite often most of the app state and state altering functions reside directly in the root component. The state and its handler methods have then been passed to other components with props. This works up to a certain point, but when applications grow larger, state management becomes challenging.
1111

1212
### Flux-architecture
1313

src/content/8/en/part8a.md

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -250,17 +250,17 @@ Let's implement a GraphQL server with today's leading library: [Apollo Server](h
250250
Create a new npm project with _npm init_ and install the required dependencies.
251251

252252
```bash
253-
npm install apollo-server@3.10.1 graphql
253+
npm install @apollo/server graphql
254254
```
255255

256-
**Note** at the time of writing (10th Dec 2022) the code used in this part is not fully compatible with the new version of the Apollo Server, and because of this, if you want everything to work smoothly you should install the version _3.10.1_. Material shall be updated to use the most recent Apollo Server in early 2023.
257256

258257
Also create a `index.js` file in your project's root directory.
259258

260259
The initial code is as follows:
261260

262261
```js
263-
const { ApolloServer, gql } = require('@apollo/server')
262+
const { ApolloServer } = require('@apollo/server')
263+
const { startStandaloneServer } = require('@apollo/server/standalone')
264264

265265
let persons = [
266266
{
@@ -285,7 +285,7 @@ let persons = [
285285
},
286286
]
287287

288-
const typeDefs = gql`
288+
const typeDefs = `
289289
type Person {
290290
name: String!
291291
phone: String
@@ -315,12 +315,14 @@ const server = new ApolloServer({
315315
resolvers,
316316
})
317317

318-
server.listen().then(({ url }) => {
318+
startStandaloneServer(server, {
319+
listen: { port: 4000 },
320+
}).then(({ url }) => {
319321
console.log(`Server ready at ${url}`)
320322
})
321323
```
322324

323-
The heart of the code is an _ApolloServer_, which is given two parameters:
325+
The heart of the code is an [ApolloServer](https://www.apollographql.com/docs/apollo-server/api/apollo-server/), which is given two parameters:
324326

325327
```js
326328
const server = new ApolloServer({
@@ -331,7 +333,7 @@ const server = new ApolloServer({
331333

332334
The first parameter, _typeDefs_, contains the GraphQL schema.
333335

334-
The second parameter is an object, which contains the [resolvers](https://www.apollographql.com/tutorials/fullstack-quickstart/04-writing-query-resolvers) of the server. These are the code, which defines <i>how</i> GraphQL queries are responded to.
336+
The second parameter is an object, which contains the [resolvers](https://www.apollographql.com/docs/apollo-server/data/resolvers/) of the server. These are the code, which defines <i>how</i> GraphQL queries are responded to.
335337

336338
The code of the resolvers is the following:
337339

@@ -426,13 +428,10 @@ The second parameter, _args_, contains the parameters of the query.
426428
The resolver then returns from the array _persons_ the person whose name is the same as the value of <i>args.name</i>.
427429
The resolver does not need the first parameter _root_.
428430

429-
430-
431-
In fact, all resolver functions are given [four parameters](https://www.graphql-tools.com/docs/resolvers#resolver-function-signature). With JavaScript, the parameters don't have to be defined if they are not needed. We will be using the first and the third parameter of a resolver later in this part.
431+
In fact, all resolver functions are given [four parameters](https://www.graphql-tools.com/docs/resolvers#resolver-function-signature). With JavaScript, the parameters don't have to be defined if they are not needed. We will be using the first and the third parameter of a resolver later in this part.
432432

433433
### The default resolver
434434

435-
436435
When we do a query, for example
437436

438437
```js
@@ -453,7 +452,6 @@ We have so far only defined resolvers for fields of the type <i>Query</i>, so fo
453452
Because we did not define resolvers for the fields of the type <i>Person</i>, Apollo has defined [default resolvers](https://www.graphql-tools.com/docs/resolvers/#default-resolver) for them.
454453
They work like the one shown below:
455454

456-
457455
```js
458456
const resolvers = {
459457
Query: {
@@ -473,13 +471,10 @@ const resolvers = {
473471
}
474472
```
475473

476-
477474
The default resolver returns the value of the corresponding field of the object. The object itself can be accessed through the first parameter of the resolver, _root_.
478475

479-
480476
If the functionality of the default resolver is enough, you don't need to define your own. It is also possible to define resolvers for only some fields of a type, and let the default resolvers handle the rest.
481477

482-
483478
We could for example define that the address of all persons is
484479
<i>Manhattan New York</i> by hard-coding the following to the resolvers of the street and city fields of the type <i>Person</i>:
485480

@@ -516,10 +511,8 @@ type Query {
516511
}
517512
```
518513

519-
520514
so a person now has a field with the type <i>Address</i>, which contains the street and the city.
521515

522-
523516
The queries requiring the address change into
524517

525518
```js
@@ -565,7 +558,6 @@ let persons = [
565558
]
566559
```
567560

568-
569561
The person-objects saved in the server are not exactly the same as the GraphQL type <i>Person</i> objects described in the schema.
570562

571563
Contrary to the <i>Person</i> type, the <i>Address</i> type does not have an <i>id</i> field, because they are not saved into their own separate data structure in the server.
@@ -615,10 +607,8 @@ type Mutation {
615607
}
616608
```
617609

618-
619610
The Mutation is given the details of the person as parameters. The parameter <i>phone</i> is the only one which is nullable. The Mutation also has a return value. The return value is type <i>Person</i>, the idea being that the details of the added person are returned if the operation is successful and if not, null. Value for the field <i>id</i> is not given as a parameter. Generating an id is better left for the server.
620611

621-
622612
Mutations also require a resolver:
623613

624614
```js
@@ -707,14 +697,13 @@ If we try to create a new person, but the parameters do not correspond with the
707697

708698
So some of the error handling can be automatically done with GraphQL [validation](https://graphql.org/learn/validation/).
709699

710-
However, GraphQL cannot handle everything automatically. For example, stricter rules for data sent to a Mutation have to be added manually.
711-
The errors from those rules are handled by [the error handling mechanism of Apollo Server](https://www.apollographql.com/docs/apollo-server/data/errors).
700+
However, GraphQL cannot handle everything automatically. For example, stricter rules for data sent to a Mutation have to be added manually. An error could be handeled by throwing [GraphQLError](https://www.apollographql.com/docs/apollo-server/data/errors/#custom-errors) with a proper
701+
[error code](https://www.apollographql.com/docs/apollo-server/data/errors/#built-in-error-codes).
712702

713-
714-
Let's block adding the same name to the phonebook multiple times:
703+
Let's prevent adding the same name to the phonebook multiple times:
715704

716705
```js
717-
const { ApolloServer, UserInputError, gql } = require('@apollo/server') // highlight-line
706+
const { GraphQLError } = require('graphql') // highlight-line
718707

719708
// ...
720709

@@ -724,8 +713,11 @@ const resolvers = {
724713
addPerson: (root, args) => {
725714
// highlight-start
726715
if (persons.find(p => p.name === args.name)) {
727-
throw new UserInputError('Name must be unique', {
728-
invalidArgs: args.name,
716+
throw new GraphQLError('Name must be unique', {
717+
extensions: {
718+
code: 'BAD_USER_INPUT',
719+
invalidArgs: args.name
720+
}
729721
})
730722
}
731723
// highlight-end
@@ -739,9 +731,9 @@ const resolvers = {
739731
```
740732

741733

742-
So if the name to be added already exists in the phonebook, throw _UserInputError_ error.
734+
So if the name to be added already exists in the phonebook, throw _GraphQLError_ error.
743735

744-
![](../../images/8/6x.png)
736+
![](../../images/8/6new.png)
745737

746738
The current code of the application can be found on [GitHub](https://github.com/fullstack-hy2020/graphql-phonebook-backend/tree/part8-2), branch <i>part8-2</i>.
747739

@@ -933,12 +925,6 @@ In some cases, it might be beneficial to name the queries. This is the case espe
933925
Through the exercises, we will implement a GraphQL backend for a small library.
934926
Start with [this file](https://github.com/fullstack-hy2020/misc/blob/master/library-backend.js). Remember to _npm init_ and to install dependencies!
935927

936-
**Note** at the time of writing (10th Dec 2022) the code used in this part is not fully compatible with the new version of the Apollo Server, and because of this, if you want everything to work smoothly you should install the version _3.10.1_:
937-
938-
```bash
939-
npm install [email protected] graphql
940-
```
941-
942928
Note also that the code does not initially work since the schema definition is not complete.
943929

944930
#### 8.1: The number of books and authors

src/content/8/en/part8b.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ We'll start with the following code for our application:
3535
import ReactDOM from 'react-dom/client'
3636
import App from './App'
3737

38-
import { ApolloClient, HttpLink, InMemoryCache, gql } from '@apollo/client'
38+
import { ApolloClient, InMemoryCache, gql } from '@apollo/client'
3939

4040
const client = new ApolloClient({
41+
uri: 'http://localhost:4000',
4142
cache: new InMemoryCache(),
42-
link: new HttpLink({
43-
uri: 'http://localhost:4000',
44-
})
4543
})
4644

4745
const query = gql`
@@ -89,15 +87,12 @@ import App from './App'
8987
import {
9088
ApolloClient,
9189
ApolloProvider, // highlight-line
92-
HttpLink,
9390
InMemoryCache,
9491
} from '@apollo/client'
9592

9693
const client = new ApolloClient({
94+
uri: 'http://localhost:4000',
9795
cache: new InMemoryCache(),
98-
link: new HttpLink({
99-
uri: 'http://localhost:4000',
100-
}),
10196
})
10297

10398
ReactDOM.createRoot(document.getElementById('root')).render(
@@ -607,7 +602,9 @@ const PersonForm = ({ setError }) => {
607602
refetchQueries: [ {query: ALL_PERSONS } ],
608603
// highlight-start
609604
onError: (error) => {
610-
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)
611608
}
612609
// highlight-end
613610
})
@@ -616,7 +613,8 @@ const PersonForm = ({ setError }) => {
616613
}
617614
```
618615
619-
<!-- 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+
620618
We can then render the error message on the screen as necessary:
621619
622620
```js
@@ -750,8 +748,6 @@ It looks bleak, but it works:
750748
Surprisingly, when a person's number is changed, the new number automatically appears on the list of persons rendered by the <i>Persons</i> component.
751749
This happens because each person has an identifying field of type <i>ID</i>, so the person's details saved to the cache update automatically when they are changed with the mutation.
752750
753-
The current code of the application can be found on [GitHub](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-4) branch <i>part8-4</i>.
754-
755751
Our application still has one small flaw. If we try to change the phone number for a name which does not exist, nothing seems to happen.
756752
This happens because if a person with the given name cannot be found,
757753
the mutation response is <i>null</i>:
@@ -820,7 +816,7 @@ useEffect(() => {
820816
821817
However, this solution does not work if the _notify_ function is not wrapped to a [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback) function. If it's not, this results in an endless loop. When the _App_ component is rerendered after a notification is removed, a <i>new version</i> of _notify_ gets created which causes the effect function to be executed, which causes a new notification, and so on, and so on...
822818
823-
The current code of the application can be found on [GitHub](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-5) branch <i>part8-5</i>.
819+
The current code of the application can be found on [GitHub](https://github.com/fullstack-hy2020/graphql-phonebook-frontend/tree/part8-4) branch <i>part8-4</i>.
824820
825821
### Apollo Client and the applications state
826822

0 commit comments

Comments
 (0)