Skip to main content

Introduction

GROQD — Formidable, We build the modern web

groqd is a schema-unaware, runtime-safe query builder for GROQ. The goal of groqd is to give you (most of) the flexibility of GROQ, with the runtime/type safety of Zod and TypeScript.

groqd works by accepting a series of GROQ operations, and generating a query to be used by GROQ and a Zod schema to be used for parsing the associated GROQ response.

An illustrative example:

import { q } from "groqd";

// Get all of the Pokemon types, and the Pokemon associated to each type.
const { query, schema } = q("*")
.filter("_type == 'poketype'")
.grab({
name: q.string(),
pokemons: q("*")
.filter("_type == 'pokemon' && references(^._id)")
.grab({ name: q.string() }),
});

// Use the schema and the query as you see fit, for example:
const response = schema.parse(await sanityClient.fetch(query));

// At this point, response has a type of:
// { name: string, pokemons: { name: string }[] }[]
// 👆👆

Since the primary use-case for groqd is actually executing GROQ queries and validating the response, we ship a utility to help you make your own fetching function. Here's an example of wrapping @sanity/client's fetch function:

import sanityClient from "@sanity/client";
import { q, makeSafeQueryRunner } from "groqd";

const client = sanityClient({
/* ... */
});
// 👇 Safe query runner
export const runQuery = makeSafeQueryRunner((query) => client.fetch(query));

// ...

// 👇 Now you can run queries and `data` is strongly-typed, and runtime-validated.
const data = await runQuery(
q("*").filter("_type == 'pokemon'").grab({ name: q.string() }).slice(0, 150)
);
// data: { name: string }[]

Using makeSafeQueryRunner is totally optional; you might find using q().schema and q().query in your own abstractions works better for you.

The Playground

We also provide a Vision-like Sanity Studio tool for experimenting with groqd queries and running them against your actual dataset.

Screenshot of groqd playground in action

The playground is a drop-in Sanity plugin, and is an easy way to test out groqd within your dataset. See the playground docs for more information on the playground.

Why groqd? 🤷‍

GROQ's primary use is with Sanity. Sanity's Content Lake is fundamentally unstructured, and GROQ (and Sanity's GROQ API) do not have any sort of GraqhQL-like type contracts.

We'd love to see advanced codegen for Sanity and GROQ. However, the end-result would likely not be as runtime type-safe as some might desire due to the flexibility of Sanity's Content Lake and the GROQ language in general.

The goal of groqd is to work around these constraints by allowing you to specify the runtime data schema for your query so that your data's type is runtime safe – not just theoretically, but empirically.