January 30, 2023 Go Serverless for GraphQL Backend With Grafbase As I write this blog we are seeing layoffs in the tech industry these days even then our industry is growing and the businesses and startups want to launch initial version of their product as quickly as possible to put their theoretical steps of growing business into practice and convert their product into an asset rather than liability. There are many ways to optimize this process and many of them are in the tech departments. One way to make this optimization possible is to implement product in a programming language that requires to write least amount of code. For this reason, and many other, Serverless web applications, websites and mobile applications are booming. I am going to demonstrate a serverless framework i.e. Grafbase in this blog. In addition, another area is that once our product is in the production we would like our customers to use our product more often. For that we have to take care of its performance. Among many approaches one of the approach is to retrieve necessary data from backend rather than send everything in a JSON format. Therefore, GraphQL seems to have done a great job for us to write queries to the backend to retrieve that data that our front end components require. As I explained above we are going to use below technologies in this article to explain them and build a small application using them. Grafbase Nextjs Apollo Client Grafbase Grafbase allows us to build GraphQL backends with zero configuration(obviously we can configure which is out of the scope of this blog post). It supports Schemas which Grafbase translates into GraphQL API. Furthermore, it also provides deployments feature using Git and Git branches. For more in depth details check out the link at the bottom.[1] Nextjs Nextjs is a Reactjs framework that solves some of the shortcomings that its parent library Reactjs had such as Server Side Rendering and SEO Optimized websites and web applications etc. For more details please click here.[2] Apollo Client Apollo Client is a JavaScript state management library. It manages and caches data using GraphQL. GraphQL is the backbone of Apollo Client. And that is what we are going to use it for in our application.[3] The Application Here I am going to write a small application for agriculture industry. This application uses data from drones and its sensors to be displayed and rendered on screen for users to manage them and analyze them to make predictions about their crops and make better decisions such as Spraying the crops on time and the health of the land etc etc. We are not going to interact with Drone and its sensors as it is out of the scope of this article. We will assume the data we have in our database is coming from drones’ sensors(). Out application consist of one page Home. In Home page there will be an html form where users can enter new drone information. It also consists of list of all the stored drones. Seems like slightly over complicated small app but it will be a fun to work on. Development Approach First we will create our project for both Grafbase backend and Nextjs front end. We will move on to the Grafbase and define a couple of Schemas. Next we will work on nextjs part where we will add Apollo Client as a dependency. Then we will create html form. Then we will first write a create query to add new drone. Then we will retrieve it from our backend. Then delete part will be added by deleting our drone from the available list of drones. In a nutshell below are the steps we are going to follow. Create Project Setup Grafbase Schemas Add Apollo Client Dependency Apollo Client Setup Build CRD Operations Create Project Let’s create our project. First create top level folder with the name grafbase-next using the command below. mkdir grafbase-next cd into that folder and create grafbase and next projects like below. cd grafbase-next mkdir grafbase cd grafbase npx grafbase init cd .. npx create-next-app next Run above commands one by one in your terminal. Setup Grafbase Schemas Grafbase allows us to build Serverless GraphQL backends. It supports Schemas which are translated into GraphQL APIs. It also supports relations and permissions out of the box. With Grafbase Schemas we can structure our data and the relationships between different Schemas. Before we dive into details of our Schemas, let’s explore a bit grafbase project a little. When we cd into that folder we will see a hidden folder .grafbase that is where our database lies. Grafbase is currently supporting SQLite only. For that reason you will be able to see SQLite files in that folder. At the root folder, there is a file named schema.graphql. This is where we will write down our schemas. In our example, we will create two Schemas names as Drone and Sprayed, however, we are not going to be used Sprayed Schema. Our Schemas will be Models by using @model directive. Grafbase provides different directives to make our lives easier such as @unique, @default etc etc. We will also have One-To-Many relationship in our Schemas. Now that you already have created the backend using the command npx grafbase init command. cd into grafbase folder and open grafbase/schema.graphql file and paste below code into that file. type Drone @model { id: ID! @unique name: String! lat: String long: String landHealth: String cropsSprayed: [Sprayed] createAt: DateTime updatedAt: DateTime! } The Schema we define using the keyword type and then we name our schema in our case it is Drone. Then to tell Grafbase that this is a model we used @model[5]schema. Schema consists of fields and in out case they are id, name, lat, long, landHealth, cropsSprayed, createdAt and updatedAt. Each field has a type and Grafbase called them scalars[4]. In our Schema id is a scalar of ID. We also have some String scalars and DateTime scalars in our Schemas. As you might have noticed for cropsSprayed field, we are using [Sprayed] Schema which means we are establishing One-To-Many relationship between Drone and Sprayed Schemas[6]. Each field has a type and Grafbase called them scalars[4]. In our Schema id is a scalar of ID. We also have some String scalars and DateTime scalars in our Schemas. As you might have noticed for cropsSprayed field, we are using [Sprayed] Schema which means we are establishing One-To-Many relationship between Drone and Sprayed Schemas[6]. Below is the Sprayed. type Sprayed @model { id: ID! @unique cropsType: String sprayedDate: Timestamp sprayType: String drone: Drone createAt: DateTime } As we can see the Sprayed Schema can have one Drone by providing drone field with Drone Schema scalar/type. Add Apollo Client Dependency Now that we have setup our backend using Grafbase it is time to move our focus on our front end i.e. nextjs. cd into next folder and add Apollo Client as a dependency by using below command. yarn add @apollo/client Apollo Client allows us to write graphQL queries on front end side. Using it we have the luxury to retrieve any amount of information that our component needs. Moreover, we can also mutate our data. In addition, it also provides caching, refetching and polling features.[3] Apollo Client Setup Now that we already have the dependency setup. We need to make Apollo client available to the rest of the application using Apollo Provider. We will do that in _app.tsx file like below. import type { AppProps } from ‘next/app'; import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client'; const client = new ApolloClient({ uri: 'http://127.0.0.1:4000/graphql', cache: new InMemoryCache(), }); export default function App({ Component, pageProps }: AppProps) { return ( <ApolloProvider client={client}> <Component {...pageProps} /> </ApolloProvider> ) } We are importing ApolloClient, InMemoryCache and ApolloProvider from apollo-client library. We are creating a new instance of ApolloClient by providing uri and cache values in an object. uri is the endpoint to our graphQL backend. Whereas cache value is used for caching our data. Caching is one of many Apollo GraphQL feature. When clients make remote request for the first time it loads data from their respective servers as on initial request the cache will be empty and before making additional requests to servers in future it checks the data in local cache if unavailable to downloads it from the servers otherwise it serves users from the local cached data and thus improving sites performance. For more in depth look into GraphQL Cache click here.[6] Build CRUD Operations HTML(JSX) Form Create, Read, Update and Delete are the most common operation of any app development and these are the one you will encounter the most. First let’s build bare bone html(JSX) form to begin with. import Link from 'next/link'; export default function Home() { return ( <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr' }}> <div> <form> <div> <label htmlFor=""> Name <input type="text" name="name" /> </label> </div> <fieldset> Coordinates to Calculate Area <div> <label htmlFor=""> Longitude <input type="text" name="longitude" /> </label> </div> <div> <label htmlFor=""> Latitude <input type="text" name="latitude" /> </label> </div> <div> <label htmlFor=""> Area <input type="number" name="area" /> </label> </div> </fieldset> <div> <button>Save</button> </div> </form> </div> <div> <table> <thead> <tr> <th>Name</th> <th>Longitude</th> <th>Latitude</th> <th></th> <th></th> </tr> </thead> <tbody> </tbody> </table> </div> </div> ) } Open your /src/pages/index.tsx file and replace the contents of that file with above code. We are using a plan html form here in which we are presenting user to enter certain information like user’s name and the coordinates of drone area. It also have a button to perform save action. Additionally, there is an html table that displays data from Grafbase SQLite database via GraphQL API end point. The information it renders is Name, Longitude and Latitude. When you run your project this is how your project should look like. b. Saving/Creating Operation After having the html form completed, it is time for us to add some interactivity to that html form. By interactivity I mean after users entering their data and hitting the save button will save the users input data by making GraphQL mutation query to Grafbase API endpoint to store the information into SQLite database. Having elaborated that let’s stay in the same /src/pages/index.tsx file and update the file’s contents with the below piece of code. import { SyntheticEvent } from 'react'; import { useQuery, useMutation } from '@apollo/client'; import { ADD_DRONE } from '@/queries'; ... const [addDroneParams, { data: aData, loading: aLoading, error: aError, }] = useMutation(ADD_DRONE); ... ... const calculateLandHealth = ({ lat, long }: { lat: number, long: number}) => { let health: number = lat * long; if (health < 0) return 'low'; return 'high'; }; const saveDrone = (event: SyntheticEvent) => { event.preventDefault(); addDroneParams({ variables: { name: event.target?.name.value, lat: event.target?.latitude.value, long: event.target?.longitude.value, landHealth: calculateLandHealth({ lat: event.target?.latitude.value, long: event.target?.langitude.value, }), } }); } ... ... <form onSubmit={saveDrone}> ... ... I am importing SyntheticEvent as we are using TypeScript and we will use this type for event param in saving operation function. We also need to mutate the data to make GraphQL query by using useMutation Apollo Client hook therefore, I am importing useMutation hook from Apollo Client. Moreover, to make the mutation query happen I am also importing ADD_DRONE which is our custom Apollo Client GraphQL query which we will get into in a bit. Inside Reactjs functional component, I am using useMutation hook which. It accepts ADD_DRONE parameter which is a query I imported from /src/queries/index.ts folder above. Moreover, I am extracting addDroneParam function as well as data, loading and error attributes which is returned by Apollo Client’s useMutation hook by executing GraphQL mutation query. Let’s move on to the first javascript function that I implemented which is calculateLandHealth. It is supposed to calculate the area that a particular drone needs to scan etc. I am using a funny logic which makes no sense however, this should do the trick for our sample project to understand basic operations. calculateLandHealth javascript function accepts two parameters i.e. lat and long. I multiply them and save the result in health variable. Then based on the health variable values I return ‘high’ or ‘low’ strings. Next I am implementing saveDrone javascript function which basically basically gets html form input fields entered by users when they clicked on save button. These values are passed as variables object to addDroneParams function from ApolloClient useMutation query. At the end I am updating the html form tag with additional html form attribute i.e. onSubmit and passing saveDrone function. This function will be triggered automatically when user clicks on html form save button. import { gql } from '@apollo/client'; export const ADD_DRONE = gql` mutation AddDrone($name: String!, $long: String, $lat: String) { droneCreate(input: { name: $name, lat: $lat, long: $long }) { drone { name long lat } } } `; You application should not work as you would have expected it to be because we still have to work the Apollo Client GraphQL mutation query which we imported at the top of index.tsx page. Let’s open /src/queries/index.tsx file or create one if not created yet and paste below code in there. In the first line, I am importing gql from ApolloClient which we will use to write GraphQL query. gql is ApolloClient function which is used to wrap the GraphQL query string and parses them into GraphQL query documents. Moving on to the GraphQL query itself, I am exporting ADD_DRONE constant variable which has the GraphQL mutation. Inside the string, I am using the mutation keyword with a function AddDrone which accepts name, lat, and long variables are String types. For creating or saving or adding new data into Grafbase SQLite database, Grafbase returns a javascript function with Schema name i.e. Drone(with small letter d) in our usecase postfixed with create word such as droneCreate. This javascript function accepts input parameter which has all the values retrieved from user via html form. Inside there we are using the Schema drone to our items. Phew, that was a lot of explanation however, you might dig deep by following the links at the bottom of this article. Feel free to do so. Run the project in your browser and enter the data like below screenshot. When you hit save you should not be able to see any changes on UI perspective however, it literally saved the data in SQLite database used by Grafbase. c. Reading Operation Time to retrieve that data from Grafbase SQLite database which we just saved above using ApolloClient GraphQL mutation query. To make that happen let’s update your /src/pages/index.tsx file with below code. ... import { useQuery, useMutation } from '@apollo/client'; import { GET_DRONES, ADD_DRONE, DELETE_DRONE } from '@/queries'; ... const { loading, error, data } = useQuery(GET_DRONES); ... <tbody> { data?.droneCollection?.edges?.map(({ node }: INodeParam, index: number) => ( <tr key={`${node?.id}-${index}`}> <td><Link href={`/detail/${node?.id}`}>{node?.name}</Link></td> <td>{node?.long}</td> <td>{node?.lat}</td> <td></td> <td></td> </tr> )) } </tbody> To retrieve data from Grafbase SQLite database we use useQuery hook from ApolloClient library. Secondly, I am importing GET_DRONE query from /src/queries/index.ts file. Furthermore, I am calling useQuery hook inside Reactjs functional component and passing GET_DRONE query as a parameter and extracting loading, error and data attributes that it returns. Later in JSX HTML table tbody tag I am mapping through data(extracted from useQuery hook) which has droneCollection and edges attributes and displaying the long, lat and name properties. After playing with /pages/index.tsx file it is time to switch to /src/queries/index.tsx file and add the GET_DRONE GraphQL query like below. ... export const GET_DRONES = gql` query getDrones { droneCollection(first: 10) { edges { node { id name lat long } } } } `; ... In order to get data from Grafbase server, we use query keyword with getDrone custom property. Inside getDrone, since we are retrieving all the data from Grafbase SQLite database therefore, Grafbase postfix the Grafbase Schema with Collection keyword i.e. droneCollection. To this function we are passing first: 10 which means it returns the first 10 results to the front end from Grafbase SQLite database. Moreover, we are returning id, name, lat, long information. To view the changes you need to refresh your page and you will stumble upon below output and see the data you entered while working to mutation operation above. d. Delete Operation At times you would want to make a delete operation which removes data permanently from databases(). Therefore, let’s add delete functionality to our application if one of our drone is out of service or malfunctioned and cannot be repaired etc then we might not need it to be listed in our database. Get back to /src/pages/index.tsx file and add below contents to it. import { GET_DRONES, ADD_DRONE, DELETE_DRONE } from '@/queries'; ... const [deleteDroneParams, { data: dData, loading: dLoading, error: dError, }] = useMutation(DELETE_DRONE); ... <td>{node?.lat}</td> <td><button type='button' onClick={() => { deleteDroneParams({ variables: { id: node?.id } }); }}>Delete</button></td> ... First of all I am importing DELETE_DRONE query. Since we are mutating our data therefore, we are going to use same useMutation ApolloClient hook and we will pass DELETE_DRONE query as a parameter and extracting the deleteDroneParam javascript function and the rest of the attributes we need it. Moreover, inside the HTML table add a delete button in actions column and call deleteDroneParam function return from useMutation ApolloClient hook and pass node.id. When we hit the delete button it will trigger this GraphQL mutation query. Now that we have the delete code in place it is time switch back to /src/queries/index.tsx file and add DELETE_DRONE query like below. ... export const DELETE_DRONE = gql` mutation DeleteDroneById($id: ID!) { droneDelete(by: { id: $id }) { deletedId } } `; ... In this GraphQL query as you can see we are using droneDelete javascript function which receives id as a parameter. Time for testing . Run you application and you should be able to see your data with a delete button like below screenshot. Upon hitting the delete button, the data should be deleted from SQLite database and when you refresh the page you should no longer see data in html table like below. Github Project Link You can find this entire project on github below. https://github.com/zafar-saleem/grafbase-nextjs Conclusion In this blog we implemented small(with none sense logic ) application which performs well for Create, Read and Delete operations. I left the Update part on you to play with this around. For Update you need to use same useMutation hook and pass the id of the item you would like to update. On UI perspective how you want to achieve this depends on you. About me https://github.com/zafar-saleem https://zafarsaleem.medium.com https://linkedin.com/in/zeesaleem Sources [1] https://grafbase.com/docs [2] https://nextjs.org [3] https://www.apollographql.com/docs/react/ [4] https://grafbase.com/docs/schema/scalars [5] https://grafbase.com/docs/schema/overview#models [6] https://grafbase.com/docs/concepts/identities [7] https://www.apollographql.com/docs/react/caching/overview apollo client database grafbase graphql html Nextjs serverless sqlite apollo-clientdatabasegrafbasegraphqlnextnextjssqlite