Scaffolding a complete React app with GraphQL

Jens Neuse

Jens Neuse

Founder @ wundergraph

Let us imagine that by writing a handful of GraphQL queries, it would be possible to build a fully functional React application. For a TODO app, we could allow users to login, post, retrieve, update and complete tasks. In this post, I'll explain how WunderGraph could be leveraged to achieve such a thing, and what the implications are.

What is WunderGraph?#

Before we dig in, let's have a quick overview of what WunderGraph is in order to fully understand the scenario.

WunderGraph is a GraphQL Engine that compiles Queries on the server, exposes them as persisted operations and then generates smart clients to help you to consume them.

While GraphQL doesn't lose its dynamic character, this approach makes using GraphQL a lot safer and more performant. Additionally, because Queries & Mutations are persisted on the server by default, you are now uniquely able to use directives in Operations.

A few example queries to illustrate:

query AllTasks($email: String! @fromClaim(name: "email")) {
queryTask(filter: {email: {eq: $email}}){
id
title
completed
}
}
mutation AddTask($title: String!, $email: String! @fromClaim(name: "email")) {
addTask(input: [{title: $title, completed: false, email: $email }]){
task {
id
title
completed
}
}
}
mutation SetTaskCompleted($id: ID!, $completed: Boolean! $email: String! @fromClaim(name: "email")){
updateTask(input: {filter: {id: [$id], email: {eq: $email}} set: {completed: $completed} }){
task {
id
title
completed
}
}
}

Above, we have 3 operations:

AllTasks - Lists all tasks for a user by their email address AddTask - To add a task for a user SetTaskCompleted - To mark a task as complete

The above three Operations are quite standard with one slight exception, They contain a custom directive: @fromClaim(name: "email"). This directive ensures that the variable $email, is always obtained from a JWT claim email.

If you're not yet familiar with the concepts of WunderGraph, this is absolutely safe because only the server (WunderGraph console) can define and persist an Operation.

This means that a user needs to be logged-in to interact with these queries & only the logged in user can create, list or update their own tasks.

Next, we use wundergen (our code generator) to generate an OpenID client that is preconfigured with our backend, all type definitions for the GraphQL schema as well as React hooks for each Operation.

This process is pretty much straight forward as we just take all the configuration and run it through a template. We can now just consume the TODO API as if it were just another function call.

Why can't we scaffold the entire site?#

Well, at the moment this is just a concept, so I want to hear your feedback first before implementing it. Let's have a look at some ideas on how we might implement this. As I said before we will only be writing GraphQL Operations so let's revisit them:

query AllTasks($email: String! @fromClaim(name: "email"))
@ui(page: "/tasks", component: TABLE)
{
queryTask(filter: {email: {eq: $email}}){
... @ui(linkTo: "/tasks/:id") {
id @ui(columnName: "ID")
title @ui(columnName: "Title")
completed @ui(columnName: "Completed")
}
}
}

The AllTasks Query will generate a table component on the /tasks route. Columns will have headers and each column will link to a task detail /tasks/:id page.

mutation AddTask(
$title: String! @ui(formType: TEXT, formName: "Title"),
$email: String! @fromClaim(name: "email")
)
@ui(page: "/tasks/new", component: FORM)
{
addTask(input: [{title: $title, completed: false, email: $email }]){
task {
id
title
completed
}
}
}

The AddTask Mutation will generate a form on the /tasks/new route. The variable $title will be filled from the form.

mutation SetTaskCompleted(
$id: ID!,
$completed: Boolean! @ui(formType: SWITCH, formName: "Completed"),
$email: String! @fromClaim(name: "email")
)
@ui(page: "/tasks/:id", component: FLAT_VIEW)
{
updateTask(input: {filter: {id: [$id], email: {eq: $email}} set: {completed: $completed} }){
task {
id
title
completed
}
}
}

The SetTaskCompleted Mutation will render a switch on the /tasks/:id page to update the task state.

Let's discuss the pros and cons#

First, it's valid GraphQL syntax. Operations might get complex quite fast though.

We're definitely limited in what we could achieve with this. However, there will be use cases where customization is not as important as the cost of development.

WunderGraph allows you to connect any possible datasource (at least that's the vision) so if you wanted to build Dashboards or Admin panels, or even a simple contact page on top of your existing APIs this could be a very convenient way to achieve it without code.

There might be quite a compelling use-case for rapid prototyping. Consider the flow: Deploy a CRUD GraphQL backend, write a bunch of Operations and your app is ready to deploy. How much faster could it be? You could then customize the scaffolded views & forms as you saw fit.

Maybe the idea to directly introduce navigation between pages is a step too far. Maybe we could just generate Components based on the Operations and let the user define Navigation manually?

But then, how would you go about customizing the pre-generated components without breaking when re-generating the code after a change to the Operations?

Would multiple templates be useful so that users could choose between Material-UI or Bootstrap etc... Would that make sense?

What do you think?#

Finally, I think this is not yet ready for implementation. I need your feedback to see if this approach is useful for you. Do you have ideas on how you could use this? Is there anything missing to make it work for you?

Please meet us on discord, GitHub or in the comments and share your opinion so we can make the right decisions.