5th Practical Class:
Persistent Backend!
Detailed look at GraphQL server set-up
In backend/src/index.js
you have this:
What is going on here:
- Express (HTTP server) is initialized
x-powered-by
header is disabled- Basic CORS are set-up
- Check if we should use DB connect or no
- Apollo (GraphQL) server is initialized
Schema
(typeDefs),resolvers
,context
andplayground
are passed to Apollo- Apollo server is added to Express as a middleware
- Port setting
- Redirect from
/
to/graphql
to simplify work with server - Start of application - on what port, and what should be outputted on successful start
Connect to server
- See 4th practical - MySQL connect
- You must update
.env
file in/backend
: - Fill in DB credentials (should be in your e-mail inbox)
- Switch
mocks
to false - Seed the DB -
backend/src/db/seed.sql
<- copy content of this file - Go to your PHPMyAdmin page (or other DB admin tool you are using), click on DB name
user_<username>
in left column -> sql (top line, 3th from left), paste content of file here ->go
- This should create
quack
anduser
tables and fill them with basic data
Mutation x Query differences
Query
serves to provide data, without mutating (changing them). REST
method would be GET
Mutation
servers for all data altering operations. REST
methods would be POST
, PUT
, DELETE
- This separation is
soft
- it is responsibility of programmer to enforce it. Eq, you can createquery
which adds new items. You definitely should not! - It helps with easy API usage and maintenance. If it is ensured, that
query
never changes the data, you can run it any time, any number of times. You cancache
it, you can use bunch of other GraphQL tools with it - Always keep in mind, for every time you write the code, you will read it ~10 times! Optimize for readability, it will save you headache down the road.
Mutation
must have at least one argument (othervise it is pointles :D)Mutation
does return data, same way asQueries
do. You can use this to pass operation result information, and / or new status of object
Input x Type
Type
describes shape of data that will be provided by API - output shape
Input
describes shape of data that API will receive - input shape
Example Type
Example Input - full CRUD
Resolver / controller
Eq the famous problem - where to put the logic?
Let's take Quack DB resolver as an example:
For a small and straight-forward functions, this code is OK, no need to optimize it. Let's have another example, this times BAD one:
As you can see, really long and ugly function. We can improve it by extracting logic to stand-alone functions:
fetchData
and prepareQuery
are named pretty generically, they need better names. But they could be extracted to separate files, and potentially reused in other resolvers or even other parts of app. Usually such files are called controller
, since they control the business logic of app.
Ideal resolver could look like this:
Error handling in GraphQL
Contrary to REST, GraphQL does not use Status Code to differentiate a successful result from an exception. In REST, one can send a response with a appropriate status code to inform the caller about the kind of exception encountered.
A GraphQL API will always return a
200 OK
Status Code, even in case of error. You'll get a5xx error
in case the server is not available at all.The standard error handling mechanism from GraphQL with return a JSON containing:
- a
data
key which contains the data corresponding to the GraphQL operation (query, mutation, subscription) invoked and - an
errors
key which contains an array of errors returned by the server, with a message and location.
- a
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 sent 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
resolvers
, before you run any custom logic. - Based on validation check result, you can either remove the value, omit it from processing, or straight up
throw Error('Validation failed!')
. Idealy with bit more info then that! - Better solution than
throw Error('Validation failed!')
is to usethrow new GraphQLError('Validation failed!)
. - 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 :)
How to work with DB on your project
- We are not using any ORM, that would normally be used with SQL DB. It is to simplify the project, ORM's caused quite a lot of pain in past years
- Store DB alteration commands in BE folder -
migrations
/seed
are common names. Idea is, that one person prepares commands which need to be run on BE to get the tables / indexes. Others then use these files to update their DB to current state - In terms of getting / sending the data to DB, let's keep it simple - write down queries to your
resolvers
orcontrolers
- It is not ideal and you could run into some issues, but it should allow you to prototype pretty fast. Just keep in mind, that this needs to be updated, if you would like to use this project as base for production-ready app