GitHub GraphQL API and TypeScript
While I was working on a my-badges project, I needed to integrate with the GitHub GraphQL API. I wanted type safety, and at beginning, I was writing TypeScript types manually. But soon, it became a pain to maintain. I noticed that the GraphQL queries were almost identical to my TypeScript types. I wanted to generate the types from the queries. So I created Megaera, a GraphQL-to-TypeScript generator.
How to generate types from GraphQL queries?
Prerequisites:
- Node.js or Deno or Bun
- Megaera:
npm install megaera
- GitHub GraphQL API Access: Obtain a personal access token with appropriate scopes.
# Step 1: Write a GraphQL Query
I decided what storing queries in separate .graphql
files will be easier. First, for my tool to
work with files. Second, my IDE has a great support for queries in a separate files (easier to debug and run
queries).
query RepositoriesQuery($login: String!) {
user(login: $login) {
repositories(first: 10) {
nodes {
name
description
stargazers {
totalCount
}
primaryLanguage {
name
}
}
}
}
}
The query uses $login
to fetch repositories for a specific user. Megaera generates also type for
variables of the query.
# Step 2: Run Megaera
Run Megaera to generate TypeScript types:
npx megaera --schema=./schema.graphql ./repositories.graphql
Megaera generates a repositories.graphql.ts
file with:
export const RepositoriesQuery = `
query RepositoriesQuery($login: String!) {
user(login: $login) {
repositories(first: 10) {
nodes {
name
description
stargazers {
totalCount
}
primaryLanguage {
name
}
}
}
}
}` as string & RepositoriesQuery;
export type RepositoriesQuery = (vars: { login: string }) => {
user: {
repositories: {
nodes: Array<{
name: string;
description: string | null;
stargazers: {
totalCount: number;
};
primaryLanguage: {
name: string | null;
} | null;
}>
};
};
};
One important thing to note is that the RepositoriesQuery
type is attached to the
query: &
. This will allow to inherit types later in our code.
# Step 3: Use the Generated Types
import { Octokit } from '@octokit/core';
import { Query, Variables } from 'megaera';
import { RepositoriesQuery } from './repositories.graphql.ts';
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
function query<T extends Query>(query: T, variables?: Variables<T>) {
return octokit.graphql<ReturnType<T>>(query, variables);
}
void async function main() {
const repositories = await query(RepositoriesQuery, { login: 'antonmedv' });
console.log(repositories);
}()
The query()
function will inherit types from the query!
Even autocomplete for variables will work too! This is cool! 😎 We don't need to specify
return type and variables types manually.
# Step 4: Fragments
When using fragments, Megaera generates separate types. Example:
fragment Repository on Repository {
name
stargazers {
totalCount
}
}
query RepositoriesQuery($login: String!) {
user(login: $login) {
repositories(first: 10) {
nodes {
...Repository
}
}
}
}
Generated TypeScript includes a Repository
type usable independently or with RepositoriesQuery
.
import { Repository, RepositoriesQuery } from './repositories.graphql.ts';
const resp = await query(RepositoriesQuery, { login });
const repositories: Repository[] = resp.user.repositories.nodes;
We can use fragment type independently in our code now. Checkout this example for fragment usage.
Difference with GraphQL Codegen
Main difference is that Megaera generates types for operations (queries and mutations), not for the whole schema. This allows you to generate types for only a part of the schema, e.g. only for a specific query. GraphQL Codegen generates types for the whole schema, which is not very useful for a large schema.
Another difference is that Megaera significantly easier to configure. Megaera requires no configuration at all. Megaera is just a CLI tool.
Conclusion
Megaera bridges TypeScript and GraphQL. I find it very useful for generating types, embedding queries, and ensuring type safety. It simplifies GraphQL workflows while maintaining scalability.
Comments
No comments yet. You can be the first one!