5th Practical Class:
Persistent Backend!
Detailed look at GraphQL server set-up
In you have this:
What is going on here:
- ApolloServer is initialized with schema as and resolvers
- function is defined
- handles creation of the graphql context, that is passed to each resolver
- In this case, adds auth header and DB instance for the DB connection
- Start of the standalone apollo server , which accepts the server instance and configuration
- In configuration basic port is set and context function is passed to attribute
- the on the background initialize an server which handles the HTTP communication
- for more control over the HTTP server, you have to use dedicated express server and from package
Connect to server
- See 4th practical - MySQL connect
- You must update file in :
- Fill in DB credentials (should be in your e-mail inbox)
- Switch to false
- Seed the DB - <- copy content of this file
- Go to your PHPMyAdmin page (or other DB admin tool you are using), click on DB name in left column -> sql (top line, 3th from left), paste content of file here ->
- This should create and tables and fill them with basic data
Mutations
- defines an entry point for write operations
- Whereas is an entry point for read operations
- This separation is - it is responsibility of programmer to enforce it. Eq, you can create which adds new items. You definitely should not!
- can accept zero to any number of arguments that is needed for mutation to be done
- Similar to mutation defines its return type, usually it returns the mutated field, but again it's dev responsibility
The is then called similarly to :
Input x Type
- describes shape of data that will be provided by API
- describes shape of data that API will receive
- Special object type that allows use objects in arguments of the mutation
Example Type
Example Input - full CRUD
Resolver
Resolver for mutations are same as for queries, they have to follow the schema, so accept defined arguments and returns the return type.
Here is an example from previous lesson with a mutation (try the code here):
Same as for more complex objects can be defined as arguments and return type.
Error handling in GraphQL
-
Contrary to REST, GraphQL does not use Status Codes to differentiate a successful result from an exception.
-
A GraphQL API will always return a Status Code, even in case of error. You'll get a in case the server is not available at all.
-
The standard error handling mechanism from GraphQL with return a JSON containing:
- a key which contains a partial data corresponding to the GraphQL operation (query, mutation, subscription) invoked and
- an key which contains an array of errors returned by the server, with a message and location.
- Partial data are usually present if more resolvers are called and only some of them throw an error
We can add simple condition to our previous code, where we use custom error and throw :
This custom handling changes just the error message. In reality we want better error handling and therefore use defined codes so we can react on them on our frontends.
It's also used with pre-defined error codes by Apollo Server, try passing a string instead of an integer.
This is the result error:
Notice the field. This one is preferred to be used for determination of the errors in order to react correctly.
To define our own codes, you pass them into the like this:
Validation
Why we should validate data on BE, when we are already doing validation on FE? Never thrust data sent over the internet! Client has full control over the app, he can send what he wants. FE side validation are there for better user experience, BE validations are for security.
- Type validation is done by GraphQL, but only type validation. GraphQL does not check the values!
- You can and should do value validation in , before you run any custom logic.
- Based on validation check result, you can either remove the value, omit it from processing, or straight up . Ideally with bit more info than that!
- There are ways how to automate common validations (email, phone number) across whole schema, by creating custom scalars. Note, you will not need this for this project :)
- There are also libraries for that:)
How to work with DB and structure project
- If you're using some ORM, the structure is mostly defined by that. So you use resolvers to validate and map the data, then pass it to the ORM operations
- It's common to use a data layer in order to work with a DB, which is separated from the resolvers.
- Then resolvers again parse data, validate and map them and then send them to the data layer
- In this project for simplicity we do all the operations on the resolvers layer.
- For small projects and prototyping it is not a problem, but in bigger project many resolvers share some of the data layer logic, so it's convenient to separate it.
Live coding
Now let's finish the Quacker mutations