DEV Community

Cover image for Full stack example using React, urql and GraphQL API - Part 2
Nicolas Penot
Nicolas Penot

Posted on

Full stack example using React, urql and GraphQL API - Part 2

In the previous post, we saw how to generate your backend, enable Google Authentication, and start a new React project with GraphQL query.

Now, let's get through the process of building a small data model and implement the corresponding GraphQL query and subscription in
React to create a real-time chat.

This tuto is for beginners. You don't need to be a senior developer or be an expert in React, SQL, and GraphQL. Agoston's backend will provide the power of using GraphQL without having to deal with its complexity.

1. Connect to the Postgres instance of your backend

Go to the Agoston.io backends view. Expand the View of the Postgres access of your backend:

Agoston | Postgres access

You will see the information to connect to your dedicated Postgres instance:

Agoston | Postgres access details

You can use the Postgres client or IDE of your choice, such as DataGrip, VS Code, pgAdmin, etc. I personally use pgAdmin which works perfectly.

2. Create your data model in the Postgres instance

Agoston is database centric. You do everything in the database, and Agoston will automatically expose it to the GraphQL endpoint.

So, create a new Postgres server in pgAdmin:

Agoston | pgAdmin add backend database

Add the connection details that you get in the Agoston Postgres access popup:

Agoston | pgAdmin add backend database details

Still, on pgAdmin, we expand the Postgres server we just created and open a new "Query tool":

Agoston | pgAdmin Query tool

And we create two tables that will handle our chat data:

CREATE TABLE IF NOT EXISTS agoston_public.chats
(
    id serial,
    name character varying(50) COLLATE pg_catalog."default" NOT NULL,
    created_ts timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT chats_pkey PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS agoston_public.chat_messages
(
    id serial,
    chat_id integer NOT NULL,
    user_id integer NOT NULL DEFAULT get_current_user_id(),
    created_ts timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    message character varying(250) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT chat_messages_pkey PRIMARY KEY (id),
    CONSTRAINT chat_messages_chat_id FOREIGN KEY (chat_id)
        REFERENCES agoston_public.chats (id),
    CONSTRAINT chat_messages_user_id FOREIGN KEY (user_id)
        REFERENCES agoston_identity.user_identities (id)
);

Enter fullscreen mode Exit fullscreen mode

Finally, grant necessary permissions for this demo:

GRANT SELECT ON TABLE agoston_public.chats TO anonymous, authenticated;
GRANT SELECT ON TABLE agoston_public.chat_messages TO anonymous, authenticated;
Enter fullscreen mode Exit fullscreen mode

3. Create a GraphQL query from the built-in GraphiQL IDE

Your Agoston backend comes with a GraphiQL IDE that helps you build your requests. Open it from the backends view:

Agoston | backend view GraphiQL

A new tab opens with GraphiQL. In GraphiQL, there is an explorer of your GraphQL schema. That's very convenient for exploring your schema and building GraphQL requests. Open it here:

Agoston | GraphiQL explorer

In the explorer, you'll see the chat tables we created before.

Agoston | GraphiQL explorer details

NOTE: Any change to the data model in Postgres will automatically be included in the GraphQL endpoint.

Now we can create the first query to retrieve the chat messages from our chat id 1:

query chatMessages($chatId: Int! = 1) {
    chatMessages(condition: {chatId: $chatId}, orderBy: ID_DESC, first: 25) {
      nodes {
        id
        createdTs
        userId
        message
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

4. Implement the GraphQL query in React

In our React application, I create not only the query but also the subscription that will listen for new messages coming in the chat messages table:

import { useQuery, useMutation, useSubscription } from 'urql';
import { gql } from '@urql/core';

// Here is a fragment that I will use to avoid 
// duplicated attributes in the query and in 
// the subscription. Because they return the 
// same attributes
const ChatMessagesFragment = gql`
  fragment MessageNode on ChatMessage {
    id
    createdTs
    userId
    message
  }
`;

// Here is the Query executed during 
// the module loading
const ChatMessages = gql`
  query chatMessages($chatId: Int!) {
    chatMessages(condition: {chatId: $chatId}, orderBy: ID_DESC, first: 25) {
      nodes {
        ...MessageNode
      }
    }
  }
  ${ChatMessagesFragment}
`;

// Here is the subscription that will 
// load new messages
const NewChatMessages = gql`
  subscription chatMessages($chatId: Int!, $topic: String!) {
    listen(topic: $topic) {
      query {
        chatMessages(condition: {chatId: $chatId}, orderBy: ID_DESC, first: 25) {
          nodes {
            ...MessageNode
          }
        }
      }
    }
  }
  ${ChatMessagesFragment}
`;
Enter fullscreen mode Exit fullscreen mode

5. Create the React component that will use the GraphQL query and subscription

function ChatBoxMessages() {

  const chatId = 1;

  // We do a first initial query to load our chat data
  const [resultQuery] = useQuery({
    query: ChatMessages,
    variables: { chatId },
  });

  // We create a subscription using the urql hook to start listening
  // to our trigger notification that will let us know when new messages are inserted
  const [resultSubscription] = useSubscription({ query: NewChatMessages, variables: { chatId, topic: `chat_messages:chat_id:${chatId}` } });

  if (resultQuery.fetching) return <p>Loading...</p>;
  if (resultQuery.error) return <p>Oh no... {resultQuery.error.message}</p>;

  var messages = []
  if (!resultSubscription.data){
    messages = resultQuery.data.chatMessages.nodes;
  }else{
    messages = resultSubscription.data.listen.query.chatMessages.nodes;
  }

  return (
    <div className="messages">
      {messages.map(message => (
        <div key={message.id}>
          <p className="message-line">  {message.createdTs.substring(0,19).replace("T", " ")} <span className='user-id'>👤 {message.userId===0?'anonymous':`User ${message.userId}`}</span> <span className="message">🗣 {message.message}</span></p>
        </div>
      ))}
    </div>
  );
};


const Chat = () => {
    return (
      <div className="chatbox">
        <ChatBoxInput />
        <ChatBoxMessages />
      </div>
    );
}

export default Chat
Enter fullscreen mode Exit fullscreen mode

Complete example available here.

Conclusion

This post shows how simple and fast the process is to get from your data model to a real-time React component using GraphQL query and subscription.

Disclaimer

I'm the maker of Agoston.io, which is a backend as a service leveraging tried-and-tested open-source solutions. It allows beginner and junior developers to leverage the power of GraphQL without having to deal with its complexity.

Agoston includes a single GraphQL endpoint, self-hosting, unified authentication, subscription, dedicated Postgres, custom code, Stripe webhooks, job scheduling, and triggers.

Top comments (0)