Log in to GraphQL Editor
What is GraphQL Zeus?
Michal

Michał Tyszkiewicz

9/20/2024

What is GraphQL Zeus?

GraphQL Zeus is codegen which, with the use of just one command, generates Typescript types for your GraphQL schema. From there even a junior frontend dev can easily handle writing type-safe requests to the backend, and do so without asking backend devs or even looking at the schema. To showcase how easy it is lets go over the installation and some basic use cases.

Installation and example query

  1. Obviously first we need to install it in our project using npm i graphql-zeus.
  2. Once it finishes installing we need to point it to our schema url using the command zeus http://example.com/your-schema-url/. Zeus will then generate types for everything in that schema and put that in a file in its folder so that you can easily import it across your project.

Queries

Now we can begin writing type safe requests. Lets say our example will be a simple query which fetches reviews which we then put into a table, list, carousel or whatever else on our website:

const reviews = await Chain('http://example.com/your-schema-url/')('query')({
  listReviews: [
    {
      author_name: true,
      rating: true,
      content: true,
    },
  ],
});

That will give us all the reviews as an array, we can then easily map over it to create some simple component like this:

{
  reviews.map((review, idx) => {
    return (
      <div key={idx} className="flex flex-col">
        <h3>{review.author_name}</h3>
        <div>{'★'.repeat(review.rating)}</div>
        <p>{review.content}</p>
      </div>
    );
  });
}

Queries follow a simple structure: we use square brackets for their contents and then:

  • in the first curly brackets we put the query payload ie. what we're sending to the backend (for example the id of the item we want to get, the locale or the access token for a login query)
  • in the second curly brackets we specify which fields we want to receive

Following our example lets say we're interested in getting our reviews in only one language - then we would have to specify that in the query payload(autocomplete is your friend here and will tell you exactly what you can input there):

const reviews = await Chain('http://example.com/your-schema-url/')('query')({
  listReview: [
    { locale: 'pl' },
    {
      author_name: true,
      rating: true,
      content: true,
    },
  ],
});

Mutations

With mutations instead of retrieving data, we're sending it to the backend to make changes. The structure is the same as with queries, two sets of curly brackets:

  • in the first brackets one we include the inputs ie. what we want to change (like add a new review or modify an existing one by id)
  • in the second brackets we specify what we want to get back after the mutation runs, which is usually a confirmation that the mutation was successful

Sticking with our previous example we can create a mutation to submit reviews:

const newReview = await Chain('http://example.com/your-schema-url/')(
  'mutation',
)({
  createReview: [
    { input: { author_name: 'John', rating: 5, content: 'Great product!' } },
    {
      id: true,
    },
  ],
});

This will only create a 5 star review by John with that content. Most likely you'd want to link that to some small form component which lets users write reviews and sends that mutation with the data on submit or something similar.

Use with CMS (Hygraph)

If we're reusing queries or mutations across our project it makes little sense to keep writing them out like that. It makes more sense to put them in a specific file and then import them as needed. For this example lets say we're pulling data from a CMS like Hygraph, we can create a file called requests.ts. Then at the top of it we can create a custom Chain which will handle the url and authorization (we'll be using it for all our queries and mutations since Hygraph wont let us through otherwise):

export const chain = Chain(process.env.CMS_URL!, {
  headers: {
    Authorization: `Bearer ${process.env.CMS_TOKEN}`,
  },
});

Now we can create a query to get all our blogposts from the CMS and even limit those to only the ones in English and only those in the Published stage o that we avoid getting drafts and such:

export const getPosts = async () => {
  return await chain('query')({
    posts: [
      {
        stage: Stage.PUBLISHED,
        locales: [Locale.en],
      },
      {
        title: true,
        author: true,
        coverImage: true,
        publishedAt: true,
        slug: true,
        content: { markdown: true },
      },
    ],
  });
};

From here using it in our component is a simple as doing a const and then putting the data in a map, list, carousel or whatever we want.

const blogposts = await getPosts();
{
  blogposts.map((blogpost, idx) => {
    return <BlogCard key={idx} />;
  });
}

GraphQL's core concept is getting only what you request without overfetching so if we want to get just one blogpost we'll need a specific query for that. Lets say we want to make dynamic pages for our blogposts using slugs, for that we'll need to pass that to the CMS in the payload and then we'll receive only the post we asked for. We also need to throw an error in case an incorrect slug was provided or something else went wrong, so lets use a simple try catch:

const getPostBySlug = async ({ slug }: { slug: string }) => {
  try {
    const result = await Chain('query')({
      post: [
        {
          stage: Stage.PUBLISHED,
          locales: [Locale.en],
          where: { slug: slug },
        },
        {
          title: true,
          author: true,
          coverImage: true,
          publishedAt: true,
          slug: true,
          content: { markdown: true },
        },
      ],
    });
    return result;
  } catch (error) {
    console.error('Could not get post, error:', error);
    throw error;
  }
};

Zeus Selectors

Writing out all the fields we want returned every single time would be cumbersome, and that's where Selectors come in. Since Zeus generates types for the entire schema it makes writing Selectors much easier with the use of autocomplete and type-safety. As we were querying for blogposts it makes the most sense to go with a Post Selector as an example:

export const postSelector = Selector('Post')({
  title: true,
  author: true,
  coverImage: true,
  publishedAt: true,
  slug: true,
  content: { markdown: true },
});

Thanks to that instead of writing out all the fields we can just use a selector like this:

export const getPosts = async () => {
  return await chain('query')({
    posts: [
      {
        stage: Stage.PUBLISHED,
        locales: [Locale.en],
      },
      postSelector,
    ],
  });
};

And that's all for the basic use cases, hopefully this guide was simple enough to follow and now you're ready to use Zeus for your projects and find out yourself how handy it is for working with the backend. I can say it definitely worked wonders for me as a very junior frontend dev. If you have any questions or want to know more, feel free to reach out and stay tuned for the next post, where I’ll dive into a recent update to Zeus and how it can make things even easier!

Check out our other blogposts

Python models in GraphQL with Rust backend in 1 line of code
Dan Stein - DJ Fresh
Dan Stein - DJ Fresh
Python models in GraphQL with Rust backend in 1 line of code
4 min read
over 1 year ago
GraphQL - schema-first vs code-first
Tomek Poniatowicz
Tomek Poniatowicz
GraphQL - schema-first vs code-first
6 min read
about 4 years ago
Is GraphQL the future of APIs?
Tomek Poniatowicz
Tomek Poniatowicz
Is GraphQL the future of APIs?
2 min read
over 5 years ago

Ready for take-off?

Elevate your work with our editor that combines world-class visual graph, documentation and API console

Get Started with GraphQL Editor