refinedev - hasura (nested/multiple query_root)

WHAT TO KNOW - Sep 8 - - Dev Community

Refined: Enhancing Hasura with Nested and Multiple Query Roots

Introduction

Hasura, a powerful open-source engine, empowers developers to build GraphQL APIs with ease, connecting to your database and automatically generating CRUD operations. This streamlines the process of building data-driven applications, saving valuable time and effort. However, sometimes you need more flexibility and control over your GraphQL schema, especially when dealing with complex queries involving nested relationships or multiple data sources. This is where Refined comes into play.

Refined is a powerful library built specifically for Hasura, extending its functionality and allowing you to define custom GraphQL resolvers, modify the generated schema, and implement complex logic within your GraphQL API. It enables you to create nested and multiple query roots, effectively breaking the constraints imposed by Hasura's default structure and unlocking new possibilities for your applications.

This article will guide you through the intricacies of using Refined with Hasura, covering its core concepts, practical examples, and best practices. We'll explore how to:

  • Define nested query roots to structure complex data interactions.
  • Leverage multiple query roots to work with different data sources.
  • Craft custom resolvers to implement logic beyond basic CRUD operations.
  • Optimize your Refined configuration for performance and maintainability. ### Diving into the Concepts

1. Nested Query Roots

In Hasura, the default query root represents the entry point for all GraphQL queries. However, this single root can become cumbersome for navigating complex data structures with deeply nested relationships. Nested query roots provide a solution by allowing you to create separate, hierarchical entry points within your GraphQL schema.

Imagine an e-commerce platform with products, categories, and reviews. Without nested query roots, accessing a product's reviews would involve querying the product, then using its ID to query reviews, leading to cumbersome queries. Using a nested query root, you can define a dedicated "Reviews" entry point directly within the "Product" object, offering a more intuitive and efficient way to retrieve reviews.

2. Multiple Query Roots

When your application interacts with multiple data sources, the default Hasura structure may feel limiting. Multiple query roots allow you to define separate entry points for each data source, effectively creating a "multi-headed" API.

Consider a scenario where your application needs to interact with a PostgreSQL database for user data and a separate MongoDB instance for product information. Using multiple query roots, you can define separate GraphQL entry points for "Users" and "Products," allowing your application to seamlessly interact with both data sources through a unified GraphQL interface.

3. Custom Resolvers

Refined empowers you to extend Hasura's functionality by writing custom resolvers. These resolvers allow you to execute complex logic, manipulate data, and create custom GraphQL fields that go beyond the basic CRUD operations generated by Hasura.

Custom resolvers can be used to perform actions like:

  • Data Transformation: Format data before returning it to the client.
  • External API Calls: Retrieve data from third-party APIs and integrate it into your schema.
  • Business Logic: Implement complex rules and calculations within your GraphQL API. ### Hands-On Guide: Implementing Refined with Hasura

This section walks you through a practical example, demonstrating how to implement nested query roots and custom resolvers using Refined and Hasura.

Scenario: We'll create a simple e-commerce platform with products, categories, and reviews, demonstrating how to structure the data relationships and add a custom field for calculating average product rating.

Prerequisites:

  • A Hasura instance with a PostgreSQL database.
  • Node.js and npm installed.
  • Basic familiarity with GraphQL and Hasura.

1. Project Setup

  • Create a new directory for your project and initialize it using npm:
   mkdir refined-hasura-example
   cd refined-hasura-example
   npm init -y
Enter fullscreen mode Exit fullscreen mode
  • Install the necessary packages:
   npm install refined-hasura hasura-client graphql
Enter fullscreen mode Exit fullscreen mode

2. Database Setup

  • Create the following tables in your PostgreSQL database:
CREATE TABLE categories (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL
);

CREATE TABLE products (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  category_id INTEGER REFERENCES categories(id)
);

CREATE TABLE reviews (
  id SERIAL PRIMARY KEY,
  product_id INTEGER REFERENCES products(id),
  rating INTEGER NOT NULL
);
Enter fullscreen mode Exit fullscreen mode
  • Configure Hasura to track these tables and generate GraphQL schema.

3. Defining Nested Query Roots

  • Create a file named refined.config.js in your project directory.
  • In this file, we'll define the nested query root for "Reviews" within the "Product" object.
const { defineSchema } = require('refined-hasura');

module.exports = defineSchema({
  query: {
    Product: {
      Reviews: {
        type: 'Review',
        query: (parent) => {
          return {
            where: { product_id: { _eq: parent.id } },
          };
        },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • defineSchema is the main function used to define your Refined configuration.
  • We define a nested query root named "Reviews" within the "Product" object.
  • type: 'Review' specifies the type of the returned data.
  • query: (parent) => ... defines a function that returns the query object for accessing reviews based on the parent "Product" object.

4. Custom Resolvers: Calculating Average Rating

  • Add a custom resolver to calculate the average rating for each product.
  • In the same refined.config.js file, add the following:
const { defineSchema } = require('refined-hasura');

module.exports = defineSchema({
  query: {
    Product: {
      Reviews: {
        type: 'Review',
        query: (parent) => {
          return {
            where: { product_id: { _eq: parent.id } },
          };
        },
      },
      averageRating: {
        type: 'Float',
        resolve: async (parent) => {
          const reviews = await context.query(
            `
              SELECT AVG(rating) FROM reviews WHERE product_id = ${parent.id}
            `
          );
          return reviews[0].avg;
        },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We define a custom field "averageRating" within the "Product" object.
  • type: 'Float' specifies the data type of the returned value.
  • resolve: async (parent) => ... defines a function that returns the average rating based on the product's ID.

5. Running Refined

  • Save your changes to refined.config.js.
  • Start the Refined server:
npx refined-hasura
Enter fullscreen mode Exit fullscreen mode
  • Access your GraphQL API endpoint (typically http://localhost:3000/v1/graphql).
  • You can now use your GraphQL client to explore the extended schema with the nested "Reviews" query root and the custom "averageRating" field.

Example GraphQL Query:

query {
  products {
    name
    averageRating
    Reviews {
      rating
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices for Refined and Hasura

  • Organized Schema: Structure your GraphQL schema thoughtfully, utilizing nested query roots to group related data and maintain clarity.
  • Custom Resolver Logic: Keep custom resolver logic focused and modular, separating business logic from data fetching for easier maintenance.
  • Caching: Implement caching mechanisms for frequently accessed data, especially for complex resolvers, to improve performance.
  • Error Handling: Implement robust error handling in your resolvers to catch potential exceptions and provide informative error messages to the client.
  • Testing: Write comprehensive tests to ensure the correctness and reliability of your Refined-powered GraphQL API.
  • Documentation: Document your GraphQL schema and custom resolvers thoroughly, making it easier for others to understand and use your API. ### Conclusion

Refined provides a powerful extension to Hasura, enabling developers to overcome limitations and craft sophisticated, customized GraphQL APIs. This article explored the core concepts of nested query roots, multiple query roots, and custom resolvers, providing practical examples and best practices for leveraging these features effectively. By embracing Refined, you gain the flexibility to tailor your GraphQL schema to your specific needs, unlocking new possibilities for building innovative and efficient data-driven applications.

As you venture deeper into the world of Refined and Hasura, remember to focus on schema organization, efficient code, and robust testing to build a powerful and maintainable GraphQL API that will serve your application well.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player