As mentioned at the end of my previous post about React Hooks API, I wanted to check out what an implementation of Hooks API on the frontend with a GraphQL backend would entail. In this post, we will go through that.

Introduction:

We make a backend server with SQLAlchemy, flask and graphql-python’s Graphene. I chose Python over NodeJS for this example’s backend as I had first started exploring GraphQL a couple of years back with python, but since the NodeJS libraries seemed easier to use, I switched and never got a chance to look back into python support for GraphQL.

On the frontend, I have used Daniel Trojanowski’s react-apollo-hooks . Its a temporary wrapper for ApolloClient using React Hooks API. Authentication is still handled via Context API as that requires sharing state rather than logic.

For interaction between server and client, I have used JWT authentication.

NOTE: Hooks API is still in alpha. As such, official ApolloClient Hooks version is not expected any time soon.

Implementation:

Graphene server:

Steps for making the basic server would be mostly similar to those mentioned in official documentation, with minor changes that are required for the nature of our example and authentication setup.

Install pip if not already installed and initiate repository ( git init )

if not already installed and initiate repository ( ) Install virtualenv: pip install virtualenv (Its always better to use virtual environments to avoid complications later)

(Its always better to use virtual environments to avoid complications later) Initiate virtualenv: virtualenv venv and add venv to .gitignore ( __pycache__ and data.sqlite should also be added to .gitignore later)

and add to ( and should also be added to later) Activate virtualenv: source venv/bin/activate (To deactivate, just use deactivate )

(To deactivate, just use ) Install flask, GraphQL, SQLAlchemy, and JWT dependencies: pip install flask flask-graphql flask-migrate flask-cors flask-sqlalchemy graphene graphene-sqlalchemy pyjwt (These dependencies can be fixed in a requirements.txt with pip freeze > requirements.txt so that any later use of the code would only require a pip install -r requirements.txt ).

(These dependencies can be fixed in a with so that any later use of the code would only require a ). Initialise database in a db.py :

Initialise the manager script in an app.py (We keep the schema related stuff commented for now so that we can get a functioning server before settings up GraphQL schema. To check that, use python server/app.py runserver . Once that is done, it can be uncommented).

Setup our models, User and Post . These would require a uuid field apart from the standard fields used with such models like {username, password, posts} and {title, body, author} . We never store the password as is in the database. So, we have methods for converting the password to/from its hash. We also keep the auth_token encoding methods in the User model (or in another utils file).

Setup GraphQL schema corresponding to these models. Specifically, we will need:

a query to get all posts mutations to execute CRUD operations on posts mutations to login and signup (These can also be done via a separate service within the application, but using mutations seems simpler to use) decorators and viewer for requiring authentication in mutations and queries respectively. (Authorisations, if required, are also best handled via decorators. But in our application, we only have one check to ensure that a post can only be deleted/updated by its author. We do that in place within the mutate methods)

Now we need to initiate migrations directory, migrate and run (Its always simpler to keep such commands in a Makefile ):

python app.py db init python app.py db migrate python app.py db upgrade python app.py runserver

This should get GraphQL server running at localhost:5000/graphql . We can explore the queries and mutations on the GraphiQL interface on this URL, but since we have authentication enabled, we will need a way to get/pass the authToken. ModHeader is a good chrome plugin to handle that.

React Client:

Basic-setup would follow the steps used in the previous example, but functionality would deviate towards ApolloClient setup instead of useReducer() hook.

Install yarn and initialise using yarn init

and initialise using Add node_modules and dist to .gitignore

and to Make public and src folders in the client directory. Add index.html to the public folder

Install babel: yarn add --dev @babel/core@7.1.0 @babel/cli@7.1.0 @babel/preset-env@7.1.0 @babel/preset-react@7.0.0

Add a .babelrc with following:

{ "presets": ["@babel/env", "@babel/preset-react"] }

Install webpack dependencies: yarn add --dev webpack@4.19.1 webpack-cli@3.1.1 webpack-dev-server@3.1.8 style-loader@0.23.0 css-loader@1.0.0 babel-loader@8.0.2

dependencies: Add a webpack.config.js to the application directory:

Add scripts to the package.json :

"scripts": {

"start": "webpack-dev-server --mode development",

"build": "webpack --mode production"

}

Add a React, MaterialUI and ApolloClient dependencies: yarn add react@16.7.0-alpha.2 react-dom@16.7.0-alpha.2 react-hot-loader@4.6.0 react-apollo-hooks@0.2.1 react-router-dom@4.3.1 @material-ui/core@3.0.1 @material-ui/icons@3.0.1 apollo-cache-inmemory@1.3.0 apollo-client@2.4.1 apollo-link@1.2.2 apollo-link-error@1.1.0 apollo-link-http@1.5.4 graphql@0.13.2 graphql-tag@2.9.2

Add index.js to src folder. This will hold substantially more functionality as compared to the last example. Briefly, it needs to:

initialise history declare server HTTP link, authentication middleware, and error-handling middleware to be used by ApolloClient initialise cache to be used by ApolloClient initialise ApolloClient client pass the history and client as props to Router and ApolloProvider wrappers respectively while rendering the App component.

Initialise authentication context in a separate file (to be imported where required).

Add authToken persistence/removal utils (We do not have much stuff within user object, so we can save and use that as is. In real-world applications, it is better to have a query/endpoint for getting user-details).

Since we have required authentication for all posts related operations, we need to have route(s) that require authentication. So we make a PrivateRoute wrapper component that additionally handles the Suspense for us.

Next, we come to the App.jsx which would be our main component. It will need to handle the logic for:

the general layout of our application declaring our main routes wrapping the application with context providers setting up HMR

Login and Signup workflows and their components are quite similar on the front end, just like their mutations are on the server side. They will have the forms for getting/submitting the username and password . These will require using useState() hook, which we used in our previous example. But instead of using a function passed as a prop to submit the payload, we will be using the mutations to send the data to the server. In order to do that, we need to declare:

mutation query to be used and wrap it with gql tag from graphql-tag outside the functional component mutation function to be used within the functional component’s business logic (we use useMutation() custom hook from react-apollo-hooks for this purpose), along with post-mutation redirects.

CRUD operations on posts are handled via /posts route. Posts component needs to:

fetch posts using allPosts query declared earlier handle createPost workflow (separated out into AddPost component) handle post display, edit, and delete (separated out into Post component)

In a real-world application, we would never show complete posts on the listing page. We would have a limited display (with limited fields queried) in a card or something similar and the post itself be opened in a separate route of the form /posts/:post_slug . Adding/Editing the post would also be done via modals or separate routes usually. But, for the purpose of this example, handling them all via a single route/view should be fine.

Posts component would require a gql-tagged query declaration for the allPosts query. To get the query object (with results and other utility methods) to be available within the functional component, we use useQuery() hook from react-apollo-hooks . Suspense used in PrivateRoute declaration handles the loading of the query results.

Post component handles display, edit, and deletion. As such, it will require gql-tagged query declarations for updatePost and deletePost mutations and their corresponding mutation function declarations using useMutation() hook within the functional component. It would require the allPosts gql-tagged query for updates post mutations. This is imported from the Posts component. We also add frontend checks for edit/delete authorisation using the AuthContext.

AddPost component ends up becoming a reduced form of Post component — it has only createPost mutation handling which is quite similar to updatePost mutation. As done earlier, we declare the createPost gql-tagged query and corresponding mutation function using useMutation() hook. We update the posts post mutation using allPosts gql-tagged query imported from Posts component.

And, we are done. The client can be run using yarn run start and will be available at localhost:3000/ . This is how the application looks like:

The code for this example is available at https://github.com/nsuthar0914/graphene-react-hooks

Observations:

Hooks API works fine with GraphQL clients as well. Concerns are well separated, the code is easier to follow and components are smaller.

Flask, Graphene, and SQLAlchemy are a cool combo. Very less code required to set up the server as compared to a similar NodeJS, Express-GraphQL and Sequelize server. And, we get migrations setup sensibly done.

There was a weird bug on the frontend with queries being skipped without error for no apparent reason. Moving apollo-cache-inmemory to v1.3.0 fixed it.

That’s it for this time. Will need to explore ways to add unit-tests to this stack.

— Neeraj Suthar