Rapid application development with Dgraph data platform - Dgraph Blog

This blog post reviews the steps needed to rapidly set up a GraphQL-based application factory. The instructions and sample code cover how to model and think about the data, set up the Dgraph database for GraphQL storage and API access, load data, and integrate it into React components to build a web app.

Our purpose here is to show how fast and easy it is to build GraphQL web apps - especially with Dgraph - and walk you through the entire process as quickly and easily as possible.

GraphQL & Dgraph: the next step in Rapid Application Development

GraphQL is an open-source query language for APIs that is gaining traction as an alternative to the more traditional REST API style for building backend web services. GraphQL exposes a single endpoint, uses a stronly-typed schema, which enables developers to query the exact dataset they need for a particular screen or data request in a single operation. GraphQL is often used alongside javascript frameworks such as React or Vue on the front end.

GraphQL’s main advantages over REST are

  • eliminate the over-fetching and under-fetching problems.
  • offer out-the-box validation and type checking.
  • fetch data in a single network and minimize round trips to the server.
  • streamline development with fewer lines of code.
  • enable agility through faster product iterations.
  • reduce the effort to produce and maintain the API documentation

By design, the GraphQL schema defines the resources available for retrieval in addition to the accepted parameters when fetching that data.

Dgraph is the distributed graph database with native GraphQL support. It is open-source, scalable, distributed, highly available, and lightning-fast.

GraphQL traditional implementations require a ’layer’ translating the GraphQL request into database queries. This usually implies coding GraphQL ‘resolvers’. Even with tools generating resolvers for specific databases the resulting translation is slow. With Dgraph no resolvers or custom queries are needed. Simply update a GraphQL schema, and all APIs are ready to go. The “resolvers” are transparently implemented in a very efficient way through graph traversal queries with native graph performance.

Rapid Application Development with GraphQL and Dgraph

One of the challenges of innovation is to be able to go fast (and to fail fast) while being prepared for success, which implies qualities such as performance, security, maintainability, and scalability. That is what Dgraph offers: accelerate your GraphQL development while securing the scalability and performance of your GraphQL APIs and data as your application grows and becomes more complex.

Let’s see how to leverage Dgraph and its native GraphQL API endpoint. We will illustrate the process with the DonorChoose public data set. Puplic schools in the US have projects in different grades and categories. Donors do donations to projects.

This post provides an overview of the implementation process. For a complete application, you may refer to the tutorial Build a Message Board App which is using a different data model about blog posts and comments.

Step 1 - Analysis

One of the first steps is probably to start brainstorming about what your application is about. It could end up with a sketch like this about the data.

It’s interesting to note that people naturally think in terms of graphs!

Step 2 - Build the GraphQL schema

From your analysis session you are ready to create a GraphQL schema:

type School {
  id: ID!
  name: String!  @search(by: [term])
  type: String!  @search(by: [hash])
  projects: [Project] @hasInverse(field: "school")
  city: City
  geoloc: Point
}
type Project {
  id: ID!
  title: String!  @search(by: [term])
  grade: String @search(by: [hash])
  category: Category
  school : School @hasInverse(field: "projects")
  donations : [Donation] @hasInverse(field: "project")
}
type Category {
  id: ID!
  name: String!
}
type Donation {
  id: ID!
  amount:Float
  donor: Donor @hasInverse(field: "donations")
  project: Project @hasInverse(field: "donations")
}
type Donor {
  id: ID!
  name: String!  @search(by: [term])
  donations: [Donation] @hasInverse(field: "donor")
}
type City {
  name: String! @id
  state: State
}
type State {
  name: String! @id
}

Your GraphQL ’types’ are the entities you have identified. The Schema provides details about the information or fields available for each entity, and definitions such as

category: Category
donations : [Donation] @hasInverse(field: "project")

Defines relationships between entities.

Refers to Dgraph documentation - GraphQL API Schema for details about the notation.

Note that we have also used the Dgraph pre-built type Point for geolocation:

Step 3 - Deploy the GraphQL schema in Dgraph

Just deploy the GraphQL Schema from the Dgraph Cloud dashboard or through our admin API.

Your backend is ready!

No code, no resolvers, no database schema to create, and no mapping between GraphQL and tables!

Step 4 - Populate your backend

Dgraph generates queries and mutations for the GraphQL schema. You can easily use mutation requests to populate your data.

mutation addSchool {
    addSchool(input: {
        type: "urban",
				name: "Thomas S Hart Middle School",
				geoloc: {
					longitude: -121.86059,
					latitude: 37.792682
				},
				city: {
					name: "Pleasanton",
					state: {
						name: "California"
					}
	            },
                projects: [
					{
						title: "Now You See Me!",
						grade: "Grades 6-8",
						donations: [
							{
								amount: 50.0,
								donor: {
									name: "Winifred Bahr"
								}
							},
							{
								amount: 100.0,
								donor: {
									name: "Karena Ardra"
								}
							},
							{
								amount: 100.000000,
								donor: {
									name: "Joelly Podvin"
								}
							}
						]
					}
				]
    }) {
        school {
            id
        }
    }
}

As Dgraph is also the graph backend you can bypass the API and leverage low level fast data loading capabilities (Dgraph documentation - Import Data) to rapidly inject large amounts of data.

Step 5 - UI stack

As GraphQL is a standard, you can use any UI technology and framework with the capability to fetch data using a GraphQL endpoint. Type safety is usually a must for rapid application development to quickly spot and address changes during your iterations: by hooking up GraphQL Code Generator to the Dgraph instance serving your GraphQL API, you can regenerate types as you iterate on the schema and use those types to lay out the UI components.

The combination of Dgraph, GraphQL Code Generator, and GraphQL client constitutes an effective app factory and a mostly declarative way to build your applications.

One of our favorite stacks as of today (2023) is probably:

Dgaph as an all-in-one backend and GraphQL API server, React.js and typescript for the frontend development, with the addition of a UI framework (react-bootstrap), Graphql-code-generator to produce the types associated with the GraphQL queries used in our application, a GraphQL client: urql, graphql-request, or apollo client, and a GraphQL aware IDE such as VScode with GraphQL language extension.

> yarn create react-app <your-app-directory> --template typescript
> cd <your-app-directory>
> yarn add react-bootstrap bootstrap
> yarn add graphql
> yarn add -D @graphql-codegen/cli
> yarn add -D @graphql-codegen/client-preset
> yarn add urql

Then configure the code generator

> yarn graphql-code-generator init

At the question where is your schema?

Copy paste your Dgraph GraphQL endpoint.

We prefer to group GraphQL operations in ts files so we set

Where are your operations and fragments? "src/**/*.ts"

And we write the generated types to gql folder:

Where to write the output? "./src/gql/"

This is codegen.ts file with the config:

import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
 overwrite: true,
 schema: "https://<your Dgraph Cloud backend>.cloud.dgraph.io/graphql",
 documents: "src/**/*.ts",
 generates: {
   "./src/gql/": {
     preset: "client",
     presetConfig: {
       fragmentMasking: false
     },
     plugins: []
   }
 }
};
export default config;

Note that we have added

presetConfig: {
       fragmentMasking: false
     }

To be able to use a common operations.ts file to declare our fragments and operations and use them in any React component.

Open the project in VSCode.

Add the GraphQL Language Extension. Create the corresponding config file “.graphqlrc.json” With the content

{
   "schema" : "https://<your Dgraph Cloud backend>.cloud.dgraph.io/graphql",
   "documents":"src/**/*.ts"
}

Restart VSCode to enable the extension on the configured Dgraph endpoint.

Add the URQL client pointing to the Dgraph GraphQL endpoint and add a Provider.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
import {createClient, Provider} from 'urql';
const root = ReactDOM.createRoot(
 document.getElementById('root') as HTMLElement
);
const client = createClient({
 url: "https://<..>.cloud.dgraph.io/graphql"
})
root.render(
 <React.StrictMode>
   <Provider value={client}>
   <App />
   </Provider>
 </React.StrictMode>
);

If you are using bootstrap, import ‘bootstrap/dist/css/bootstrap.min/css’ in index.ts.

That’s it you have created a nice factory based on Dgraph GraphQL endpoint:

You are ready to declare GraphQL fragments and query in a ts file and use the generated types in a React component.

Step 6 - Let the GraphQL magic happen

Let’s imagine that in your application has a component to search for schools and see the list of associated projects.

You may sketch a simple UI component like the following:

We just have to declare the GraphQL request that would fetch the data needed for this component, and only the data needed.

import { graphql } from "../gql";
export const SchoolFragment = graphql(`
  fragment SchoolItem on School {
    id
    name
    type
    projects {
        title
    }
  }
`)
export const SchoolslByTermDocument = graphql(`
  query schoolsByTerm($term: String!) {
    querySchool (filter: {name: {allofterms: $term}}, first: 10) {
        ...SchoolItem
    }
  }
`)

Use the codegenerator to generate the associated types

Note that you can use the option --watch to have codegen detecting your changes and generating the types automatically.

You can now write your component

const Schools = (props:SchoolsProps) => {
    const [searchTerm, setSearchTerm] = useState<string>('');
    const [schools, setSchools] = useState<SchoolItemFragment[]>([]);
    //
    // Fetch GraphQL Data using URQL client
    //
    const [{ data, fetching, error }] = useQuery( {
        query: SchoolsByTermDocument,
        variables: {
          term: searchTerm
        }
      })
    // 
    // uddate Schools array when we have new data
    //
    useEffect( ()=> {
       console.log(`data changed ${data?.querySchool?.length}`)
       var schools = new Array<SchoolItemFragment>();
       data?.querySchool?.map((e,i) => e && schools.push(e));
       setSchools(schools)
    }, [data])
    
    return(
            <>
            <Container> <br></br>
            <Form className="d-flex">
                <Form.Control
                  type="search"
                  placeholder="Search"
                  className="me-2"
                  aria-label="Search"
                  onChange={(event)=>setSearchTerm(event.target.value)}
                />
              </Form>
            
            <br></br>
            <Accordion>
              {schools && schools.map((e,i) => e && <Accordion.Item eventKey={`${i}`}>
              <Accordion.Header>{e.name} ({e.projects?.length}) </Accordion.Header>
              <Accordion.Body>
                <ListGroup variant="flush">
                  { e.projects && e.projects.map((e,i) => e && <ListGroup.Item>{e.title}</ListGroup.Item>)}    
               </ListGroup>
              </Accordion.Body>
              </Accordion.Item>)}     
            </Accordion>
            </Container>
            </>
        )
}

The data fetching is only 4 lines of code!

And with VScode you get autocompletion of field names based on GraphQL’s strong typing, to avoid typos or errors.

You are ready to iterate safely and rapidly on your application:

  • Adding new components is simplified by GraphQL operations.
  • With Dgraph you can update you GraphQL schema in a second by deploying a new version. You don’t have to update any backend, mapping, or resolver.
  • Typescript code generation will tell you immediately if you have things to correct in your code due to your new schema version.

Going further

This short set of steps shows how easy it is get moving quickly with GraphQL, Dgraph and React.

To build a fully-featured app, you will need more features, such as security and authentication. You can use Dgraph’s built-in User and Group entities to build a user/password login, or integrate with any login system that can generate a JWT token such as Oauth0, AWS Cognito, or others.

For even more fine-grained, attribute-based access control (ABAC), you will want to use the identity of the users to enforce access to specific resources. Dgraph allows you to create sophisticated access control rules using the @auth directive.

You can also leverage GraphQL Subscriptions to have real-time notifications in your application when data is changing.

Your GraphQL API is probably not replacing all your REST APIs ! With Dgraph Lambda, you can integrate RESTful Data into your GraphQL responses. Check out this blog for more details. Dgraph lambdas provide a way to augment any GraphQL response using custom logic written in JavaScript - still conforming to a strongly-typed GraphQL schema and still executed using the GraphQL API.

Photo by picjumbo.com


This is a companion discussion topic for the original entry at https://dgraph.io/blog/post/rapid-app-dev/

A companion video: Rapid Application Development with GraphQL - Dgraph - React.js - YouTube

This blog has been created with urql version 3.
urql@4.0.0 has been released in April 2023. Refer to URQL documentation Setting up the Client

To initialize the client in version 4, use the following code in the index.tsx file

import { Client, cacheExchange, fetchExchange } from 'urql';

const client = new Client({
  url: 'https://<your Dgraph Cloud backend>.cloud.dgraph.io/graphql',
  exchanges: [cacheExchange, fetchExchange],
});