nitrogql logonitrogql

Using GraphQL in TypeScript projects

After installing nitrogql to your TypeScript project, you can start using GraphQL in your project. This page guides you how to use GraphQL with nitrogql.

๐Ÿง‘โ€๐Ÿซ Currently, this guide assumes that you are already familiar with GraphQL.

๐Ÿ’ก Check out our examples for a working example.

Writing your schema

nitrogql is a tool for the schema-first approach to GraphQL. This means that you write your schema first to define your GraphQL API.

Place your schema files in the directory specified by the schema field in the configuration file.

# ./schema/todo.graphql
"One todo item."
type Todo {
  "ID of this todo item."
  id: ID!
  "Contents of this todo item."
  body: String!
  "When not null, date when this item was marked done."
  finishedAt: Date
  "Date when this item was created."
  createdAt: Date!
}

Writing operations

Once you have your schema, you can write GraphQL operations. Place your operation files in the directory specified by the documents field in the configuration file.

nitrogql recommends having separate operation files (.graphql) instead of embedding them in your code. This is the only way of writing GraphQL operations in nitrogql.

# ./app/getTodos.graphql
query getTodos {
  todos {
    id
    body
    createdAt
    finishedAt
  }
}

Statically checking GraphQL files

To check GraphQL files, run the following command in your project directory:

npx nitrogql check

๐Ÿ’ก Note: nitrogql CLI looks for the configuration file in the current directory by default. To specify the location of the configuration file, use the --config-file option.

If all GraphQL files are valid, the command will exit with code 0. Otherwise, it will print errors and exit with code 1.

Generating TypeScript files

nitrogql generates several TypeScript files from GraphQL files:

Schema type definition file is configured by the generate.schemaOutput option. It is a single .d.ts file that contains type definitions for all types in the schema, so it must be generated.

Resolver type definition file is configured by the generate.resolversOutput option. It is a single .d.ts file that contains type definitions for all resolvers. It is optional, and only generated when you specify the option. Resolver types are useful when you are developing a GraphQL server.

Server GraphQL schema file is configured by the generate.serverGraphqlOutput option. It is a .ts file that exports the entire GraphQL schema as a single string (in SDL format). It can be used at runtime to initialize a GraphQL server without having to manually load and concatenate all schema files. It is optional, and only generated when you specify the option.

Operation type definition files are generated for each operation file. They are always emitted as long as you configure nitrogql to load operation files (via the documents option). By default, operation type definition files are .d.graphql.ts files and are placed next to each operation file.

๐Ÿ’ก Note: .d.graphql.ts files are supported by TypeScript 5.0 or later. If you are using TypeScript 4.x, you need to configure nitrogql to generate .d.ts files instead. See Configuration for details.

To generate these files, you need to specify the location of generated files in the configuration file:

schema: ./schema/*.graphql
documents:
extensions:
  nitrogql:
    generate:
      # Specify the location of generated schema file.
      schemaOutput: ./src/generated/schema.d.ts
      # Specify the location of generated resolver file.
      resolversOutput: ./src/generated/resolvers.d.ts
      # Specify the location of generated server GraphQL schema file.
      serverGraphqlOutput: ./src/generated/graphql.ts
      

There is no option to specify the location of generated types for operation files; they are always placed next to operation files.

Then, run the following command in your project directory:

npx nitrogql generate

๐Ÿ’ก Note: the generate command implies check. If there are errors in GraphQL files, the command fails and does not generate any files.

After a successful run, you will see generated files in the specified locations.

Screenshot of VSCode showing generated types
Type definitions and source maps are generated next to each operation.

Using generated types from client code

With the help of the generated operation type definition files, nitrogql allows you to use GraphQL operations type-safely in your TypeScript code. To use them, you directly import .graphql files in your code.

However, probably you need to adjust your tsconfig.json so that TypeScript allows importing .graphql files.

 {
   "compilerOptions": {
     // ...
+    "allowArbitraryExtensions": true,
     // ...
   }
 }

After configuring TypeScript correctly, it's time to import .graphql files. With default settings, these files export a TypedDocumentNode object as a default export. You can use it with any GraphQL client library. Below is an example with Apollo Client and React:

import { useQuery } from "@apollo/client";
import getTodosQuery from "./getTodos.graphql";

export function SampleComponent() {
  const { data } = useQuery(getTodosQuery);
  
  return <ul>{
    data?.todos.map(
      (todo) => <li key={todo.id}>{todo.body}</li>
    )
  }</ul>;
}

In this example getTodos.graphql is an operation file that contains a GraphQL query (query getTodos { ... }). By passing the exported query object to useQuery, you can execute the query.

Of course this is type-safe. The type of the query result, data, precisely matches the shape of the query. This means that if your code tries to access a field that does not exist in the schema, or is not fetched by the operation, TypeScript will report an error.

Using generated types from server code

In the schema-first approach to GraphQL, you develop a GraphQL server so that it implements the schema you wrote. Typically this is done by writing resolvers. The generated resolver type definition file helps you write resolvers in a type-safe manner.

๐Ÿฐ Below guide uses Apollo Server as an example, but you can use any GraphQL server library.

With nitrogql, the basic setup of a GraphQL server will look like:

import { ApolloServer } from "@apollo/server";
// server GraphQL schema file
import { schema } from "@/app/generated/graphql";
// resolver types
import { Resolvers } from "@/app/generated/resolvers";

// Context is an object that is passed to all resolvers.
// It is created per request.
type Context = {};

// define all resolvers.
const resolvers: Resolvers<Context> = {
  Query: {
    todos: async () => { /* ... */ }
  },
  Mutation: {
    toggleTodos: async (_, variables) => { /* ... */ }
  },
  // ...
};

const server = new ApolloServer({
  typeDefs: schema,
  resolvers,
});
// ...

Of course, you can use any TypeScript technique you know to organize/structure your code. For example, you might want to define Query resolvers and Mutation resolvers separately:

const queryResolvers: Resolvers<Context>["Query"] = {
  // ...
};
const mutationResolvers: Resolvers<Context>["Mutation"] = {
  // ...
};
const resolvers = {
  Query: queryResolvers,
  Mutation: mutationResolvers,
  // ...
};

However, at this step the generated resolver type definition is not practically usable because it does not allow use of default resolvers. This means that you need to define resolvers for every single field of every object type in the schema. This isn't what you usually do.

To mitigate this problem, nitrogql provides the nitrogql:model plugin. This plugin allows you to use a @model directive in your schema to mark a field as included in the model. Fields with this directive are to be resolved by default resolvers, so you don't need to define resolvers for them.

This may not be something familiar to you, but it is needed for making it practical to write resolvers while maintaining the perfect type safety.

For details about the nitrogql:model plugin, see nitrogql:model plugin.

Watching and generating types automatically

It is tedious to run generate command every time you change GraphQL files. Unfortunately, nitrogql does not provide a built-in way to watch GraphQL files and generate types automatically. However, you can use chokidar-cli or similar tools to watch GraphQL files and run generate command automatically:

chokidar '**/*.graphql' --initial --command 'npx nitrogql generate'

Alternatively, you can use Run on Save VSCode extension to run generate command automatically when you save a GraphQL file. Example configuration:

{
  "emeraldwalk.runonsave": {
    "commands": [
      {
        "match": "\\.graphql$",
        "cmd": "npx nitrogql generate"
      }
    ]
  }
}

๐Ÿงบ Read Next: Configuration, CLI Usage