Lädt...


🔧 Building a composable query generator for GraphQL


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

In a lot of newer projects, we use our GraphQL API. This is the same API you use when you're building Aha! Develop extensions. GraphQL has given us a more consistent, more flexible, and often more efficient way to access the data inside an Aha! account.

GraphQL queries are just strings:

const query = `{
  feature(id: "DEMO-29") {
    id
    name
  }
}`;

const response = await fetch("/api/v2/graphql", {
  "headers": {
    "Content-Type": "application/json",
  },
  "method": "POST",
  "body": JSON.stringify({ query }),
});

Strings are easy to start with and work well for simple, mostly static queries. But what about something more complex? What if you wanted the name of the person assigned to each feature but not when you're looking at a list of your own features? What if only some teams used sprints and you didn't want sprint information if a team didn't use them? How could you easily make wildly customizable views and only fetch the data you needed for that specific view?

Simple interpolation is one option:

const sprintQuery = sprintsEnabled ? `{
  sprint {
    id
    name
    startDate
    duration
  }
}` : "";

const query = `{
  features {
    id
    name
    ${ sprintQuery }
  }
}`;

But this can quickly get out of hand. You need to know in advance how a query can be modified so you can be sure to leave room for strings to be interpolated inside of them. Handling optional arguments can be a pain. So if strings aren't the right option for very dynamic queries, what is?

The next step beyond strings

There's a common solution to this problem: Store the information you need to make your final decisions in a more raw form that you can look at. Keep them in a structure — an introspectable form — until you're ready to generate a result.

What does that look like, though?

Say a part of your code wanted a few fields from a Feature API, "id", and "name". Some other code wants the reference number too. Instead of building a query as a string and interpolating fields into it, like this:

const query = `{
  features {
    id
    name
    ${ maybeReferenceNumber }
  }
}`;

You could keep a list of fields:

const fields = ["id", "name"];

Other parts of your system could add to it:

fields.push("referenceNumber");

And when you generate your query, the query has all of the information it needs right at hand:

const query = `{
  features {
    ${ fields.join("\n") }
  }
}`;

This seems like a minor change, and it is, but it's powerful. An object, which is easy to work with, keeps track of the decisions you've made up to this point. Your query generation is simple — it just does what the object tells it to do. Here, that's turning an array of names into fields in the query. This opens up a lot of possibilities but first we can clean it up.

How to store the query state

If you've ever used Rails, you'll know that it's great at building queries over time. You call methods to build up a query and only fire it off when you're done with it:

query = account.features
query = query.where(project_id: project.id) if project
query = query.where(release_id: release.id) if release
# ...

I've been surprised that more languages or frameworks haven't been influenced by this query building. To me, it's one of the best parts of Rails.

You can build something like this with GraphQL, though, keeping your decisions in an object and generating a query at the last minute. Imagine a Query class. We'll keep it simple to start, just holding a query type and a list of fields:

class Query {
  constructor(type) {
    this.type = type;
    this.fields = new Set();
  }
}

Next, you can implement a simple "select" method to select ID and name:

class Query {
  // ...
  select(fields) {
    fields.forEach(field => {
      this.fields.add(attr);
    });

    return this;
  }
}

let query = new Query("features");
query.select(["id", "name"]);

You can pass that query object around and other functions can add to that query object:

query.select(["referenceNumber"]);

Now, when you need it, the Query object can generate a query string. Just like the example above:

class Query {
  // ...
  toString() {
    return `{
      ${ this.type } {
        ${ Array.from(this.fields).join("\n") }
      }
    }`;
  }
}

Once you have this string, you can hand it to whichever GraphQL library you're using as if you wrote it yourself.

With that simple pattern, you can go further. You can add arguments to filter the results:

class Query {
  constructor(type) {
    // ...
    this.filters = {};
  }

  // ...
  where(filters = {}) {
    this.filters = { ...this.filters, ...filters };
    return this;
  }

  processFilters(filters) {
    // Very basic conversion, can be improved as necessary
    return Object.keys(filters)
      .map(k => `${k}: ${JSON.stringify(filters[k])}`)
      .join(', ');
  }

  toString() {
    let args = '';
    if (Object.keys(this.filters).length > 0) {
      args = `(filters: {${this.processFilters(this.filters)}})`;
    }

    return `{
      ${ this.type }${ args } {
        ${ Array.from(this.fields).join("\n") }
      }
    }`;
  }
}

let query = new Query("features");
query.select(["id", "name"]).where({ assigneeId: "12345" }).toString();

You can add subqueries to select information from child objects:

class Query {
  constructor(type) {
    // ...
    this.subqueries = [];
  }

  // ...
  merge(subquery) {
    this.subqueries.push(subquery);
    return this;
  }

  toString(options = {}) {
    const { asSubquery } = options;

    let args = '';
    if (Object.keys(this.filters).length > 0) {
      args = `(filters: {${this.processFilters(this.filters)}})`;
    }

    const subqueryString = `${this.type}${args} {
        ${Array.from(this.fields).join('\n')}
        ${this.subqueries.map(s => s.toString({ asSubquery: true })).join('\n')}
      }`;

    if (asSubquery) {
      return subqueryString;
    } else {
      return `{
        ${subqueryString}
      }`;
    }
  }}

And now you can build arbitrarily complicated queries based on whatever state you have:

let query = new Query('features');
query.select(['id', 'name']).where({ assigneeId: '12345' });

if (projectId) {
  query.where({ projectId });
}

if (includeRequirements) {
  let requirementsQuery = new Query('requirements');
  requirementsQuery.select(['id', 'name', 'position']);

  query.merge(requirementsQuery);
}

query.toString();

From now on, any time you want to change a query based on information you have someplace else, it's as easy as calling a method.

What's next?

This query generator is simple and is still missing a lot, but the core idea is easy to extend to fit your API:

  • You can add functionality for sorting, passing query arguments, pagination, etc.
  • You can have it generate GraphQL fragments and variables instead of a single string.
  • You can add another layer. For example, have your query framework generate Apollo documents instead of strings and you can get better editor integration and error messages.
  • You can create a mutation builder, tracking changes you make to your JavaScript objects and generating mutations based on those changes.

We've done all of those things, and found a lot of value in a flexible generator like this. Building a query generator ourselves also allowed us to integrate it more closely with other parts of our application framework. It's a nice simple base we could build off of easily.

And once you have this base — an introspectable object that can generate the strings you need — your code no longer needs to worry about the actual queries. It's just responsible for the decision-making and the generator does the rest.

Aha! is happy, healthy, and hiring. Join us!

We are challenged to do great work and have fun doing it. If you want to build lovable software with this talented and growing team, apply to an open role.

...

🔧 Building a composable query generator for GraphQL


📈 63.31 Punkte
🔧 Programmierung

🎥 Creating a GraphQL Server, Part 1: Building a GraphQL Server with Apollo GraphQL


📈 47.65 Punkte
🎥 Video | Youtube

🔧 Intro to GraphQL, Part 2: Exploring a GraphQL Endpoint | Learning GraphQL


📈 40.58 Punkte
🔧 Programmierung

🔧 Intro to GraphQL, Part 1: What is GraphQL | Learning GraphQL


📈 40.58 Punkte
🔧 Programmierung

🕵️ CVE-2023-50730 | graphql/grackle GraphQL Query stack-based overflow


📈 37.83 Punkte
🕵️ Sicherheitslücken

🕵️ CVE-2023-28867 | graphql-java GraphQL Query stack-based overflow


📈 37.83 Punkte
🕵️ Sicherheitslücken

🔧 Effortless Type-Safe GraphQL SDK Generation with GraphQL SDK Generator


📈 37.63 Punkte
🔧 Programmierung

🔧 Building composable applications: Playing with building blocks


📈 35.5 Punkte
🔧 Programmierung

🔧 CodeSOD: Query Query Query


📈 32.32 Punkte
🔧 Programmierung

🔧 Building Resilient Security Systems: Composable Security


📈 28.44 Punkte
🔧 Programmierung

🔧 Netlify launches new unified platform for building composable web architectures


📈 28.44 Punkte
🔧 Programmierung

🔧 Building Composable Commerce with Nuxt, Shopify, and Storyblok Crash Course Part Three


📈 28.44 Punkte
🔧 Programmierung

🔧 Building Composable Commerce with Nuxt, Shopify, and Storyblok Crash Course Part Two


📈 28.44 Punkte
🔧 Programmierung

🔧 Building Composable Commerce with Nuxt, Shopify, and Storyblok Crash Course Part One


📈 28.44 Punkte
🔧 Programmierung

🔧 Building a SQL Query Generator Using ToolJet + Gemini API


📈 28.42 Punkte
🔧 Programmierung

🔧 Building a SQL Query Generator Using ToolJet + Gemini API


📈 28.42 Punkte
🔧 Programmierung

📰 MicroProfile GraphQL 1.0 bietet APIs für Java-Applikationen auf GraphQL-Basis


📈 27.05 Punkte
📰 IT Nachrichten

🔧 Curious Use Cases of GraphQL (and The Future of GraphQL)


📈 27.05 Punkte
🔧 Programmierung

🎥 Intro to GraphQL, Part 1: What is GraphQL


📈 27.05 Punkte
🎥 Video | Youtube

🎥 Intro to GraphQL, Part 2: Exploring a GraphQL Endpoint


📈 27.05 Punkte
🎥 Video | Youtube

🎥 Creating a GraphQL Server, Part 2: Publishing a GraphQL Server to Azure App Service


📈 27.05 Punkte
🎥 Video | Youtube

🎥 Creating a GraphQL Server, Part 3: Publishing a GraphQL Server to Azure Functions


📈 27.05 Punkte
🎥 Video | Youtube

🔧 From REST To GraphQL (aka GraphQL in Production)


📈 27.05 Punkte
🔧 Programmierung

🔧 Elevate Your GraphQL API: Mastering File Uploads with Yoga GraphQL


📈 27.05 Punkte
🔧 Programmierung

📰 heise+ | GraphQL-APIs mit GraphQL Editor designen


📈 27.05 Punkte
📰 IT Nachrichten

🔧 How To Get Type-Safety Frontend Queries Like GraphQL Without GraphQL Using Typescript


📈 27.05 Punkte
🔧 Programmierung

🕵️ CVE-2023-28877 | VTEX apps-graphql 2.x GraphQL API Module improper authorization


📈 27.05 Punkte
🕵️ Sicherheitslücken

🔧 Putting The Graph In GraphQL With The Neo4j GraphQL Library


📈 27.05 Punkte
🔧 Programmierung

🕵️ Mirumee Saleor 2.0.0 GraphQL API /graphql/ information disclosure


📈 27.05 Punkte
🕵️ Sicherheitslücken

🔧 GraphQL, Simplified (GraphQL-hooks Workshop)


📈 27.05 Punkte
🔧 Programmierung

🔧 A Comprehensive Guide to Writing Your First GraphQL Query


📈 24.3 Punkte
🔧 Programmierung

🎥 Query a Database using GraphQL from your Static Web Apps [Part 22] | Azure Tips and Tricks


📈 24.3 Punkte
🎥 Video | Youtube

matomo