gqlkit Documentation

website·Indexed 31 days ago

https://gqlkit.izumin.dev

A TypeScript-first library that generates GraphQL schemas from TypeScript types and functions. It supports object, enum, interface, and union types, as well as queries, mutations, and subscriptions, with integrations for graphql-yoga, Drizzle ORM, Prisma, and Vercel AI SDK.

Tokens
14.1K
Snippets
93
Records
99
Agent score
53%

What's inside gqlkit

  1. Overview of gqlkit core concepts and design

    gqlkit is a TypeScript-first tool that allows developers to write types and resolvers in TypeScript and generate a GraphQL schema AST and resolver map. It follows an 'implement first' philosophy to avoid edit-regenerate-implement loops. Key design principles include using plain TypeScript (no decorators or complex generics), ensuring type safety where TS types become GraphQL types, and maintaining compatibility with graphql-tools via makeExecutableSchema.
  2. Integrate Drizzle ORM with gqlkit for type-safe schemas

    gqlkit integrates with Drizzle ORM by using InferSelectModel and InferInsertModel to derive GraphQL types directly from database table definitions. This ensures that your GraphQL API remains in sync with your database schema.
    npm install drizzle-orm postgres
  3. Define GraphQL Object types from TypeScript

    Plain TypeScript type exports in src/gqlkit/schema/ are converted to GraphQL Object types. Use union with null for nullable fields and standard arrays for GraphQL lists. Inline object literals are automatically converted to separate GraphQL Object types using the {ParentTypeName}{PascalCaseFieldName} convention.
    // src/gqlkit/schema/user.ts
    import { defineQuery, defineField } from "../gqlkit";
    import type { IDString, Int, NoArgs } from "@gqlkit-ts/runtime";
    
    export type User = {
      id: IDString;
      name: string;
      age: Int;
      email: string | null; // Nullable field
      tags: string[];       // List [String!]!
      profile: {
        bio: string;
        age: number;
      };
    };
    
    export const me = defineQuery<NoArgs, User | null>((_root, _args, ctx) => {
      return findUserById(ctx.currentUserId);
    });
    
    export const profileUrl = defineField<User, NoArgs, string>((parent) => {
      return `https://example.com/users/${parent.id}`;
    });
  4. Define custom GraphQL directives using GqlDirective

    Use the GqlDirective<Name, Args, Location> utility type from @gqlkit-ts/runtime to define custom directives. The Name is the directive name without the @ symbol, Args defines the typed arguments, and Location specifies where the directive can be applied (either as a single string or an array of strings).
    import { type GqlDirective } from "@gqlkit-ts/runtime";
    
    export type Role = "USER" | "ADMIN";
    
    // Directive with typed arguments
    export type AuthDirective<TArgs extends { role: Role[] }> = GqlDirective<
      "auth",
      TArgs,
      "FIELD_DEFINITION"
    >;
    
    // Directive with multiple locations
    export type CacheDirective<TArgs extends { maxAge: number }> = GqlDirective<
      "cache",
      TArgs,
      ["FIELD_DEFINITION", "OBJECT"]
    >;
  5. Use inline payload types for field returns

    Field return types can use inline object literals, which generate GraphQL Object types named {ParentTypeName}{PascalCaseFieldName}Payload.

    If a union of inline object literals is used, a GraphQL Union type is generated. Each union member must include a __typename property.

    // Object Payload
    export const statistics = defineField<
      User,
      NoArgs,
      { postCount: number; followerCount: number; isActive: boolean }
    >((parent, _args, ctx) => ctx.db.getUserStatistics(parent.id));
    
    // Union Payload
    export const verification = defineField<
      User,
      NoArgs,
      | { __typename: "Verified"; verifiedAt: string }
      | { __typename: "Unverified"; reason: string }
    >((parent) => parent.verification);
  6. Derive GraphQL types from Prisma models

    gqlkit can derive GraphQL types directly from Prisma's generated model types. Define your schema in prisma/schema.prisma and export the model type in your gqlkit schema directory. Enum types in Prisma are automatically converted to GraphQL enums.
    // src/gqlkit/schema/user.ts
    import type { Prisma } from "../../__generated__/prisma/client.js";
    
    // Export as GraphQL object type
    export type User = Prisma.UserModel;
  7. Define computed fields with defineField

    Use defineField to add computed fields to object types. The GraphQL field name is derived from the export name (using the part after the last $ if present).

    defineField<TParent, TArgs, TResult> parameters:

    • TParent: The parent object type this field is defined on.
    • TArgs: The arguments type (use NoArgs if no arguments).
    • TResult: The return type of the field.
    import { defineField } from "../gqlkit";
    import type { IDString, NoArgs } from "@gqlkit-ts/runtime";
    import type { Post } from "./post.js";
    
    export type User = {
      id: IDString;
      name: string;
    };
    
    /** Get posts authored by this user */
    export const posts = defineField<User, NoArgs, Post[]>(
      (parent) => findPostsByAuthor(parent.id)
    );
  8. Define GraphQL Query fields

    Use defineQuery to create fields for the GraphQL Query type. The GraphQL field name is derived from the exported variable name. If the export name contains a $, gqlkit uses the substring after the last $ as the field name. If the export name ends with $, an error is reported.
    import { defineQuery } from "../gqlkit";
    import type { NoArgs } from "@gqlkit-ts/runtime";
    import type { User } from "./user";
    
    // GraphQL field name: me
    export const me = defineQuery<NoArgs, User | null>(
      (_root, _args, ctx) => {
        return ctx.db.findUser(ctx.userId);
      }
    );
    
    // GraphQL field name: user(id: String!)
    export const user = defineQuery<{ id: string }, User | null>(
      (_root, args, ctx) => {
        return ctx.db.findUser(args.id);
      }
    );
    
    // GraphQL field name: users (using $ delimiter)
    export const Query$users = defineQuery<NoArgs, User[]>(() => []);
  9. Resolve concrete types for interfaces at runtime

    GraphQL requires a way to determine the concrete type of an object implementing an interface.

    Automatic Resolution: If all implementing types have __typename or $typeName fields with string literal values, gqlkit generates the resolveType function automatically.

    Manual Resolution: If types lack these fields, use defineResolveType to manually specify the logic for determining the type based on the object's properties.

    import { defineResolveType } from "../gqlkit";
    
    export const nodeResolveType = defineResolveType<Node>((value) => {
      if ("name" in value) return "User";
      if ("title" in value) return "Post";
      throw new Error("Unknown Node type");
    });
  10. Add computed fields to interface types using defineField

    Use defineField to add computed fields to an existing type or interface. The function takes the parent object as an argument and returns the field value. Use NoArgs from @gqlkit-ts/runtime if the field does not require any arguments.
    import { defineField } from "../gqlkit";
    import type { NoArgs } from "@gqlkit-ts/runtime";
    import type { Node } from "./node.js";
    
    /** Get the typename of a Node */
    export const __typename = defineField<Node, NoArgs, string>(
      (parent) => parent.constructor.name
    );
  11. Register resolvers for custom scalars

    Custom scalars require runtime resolver implementations for serialization and parsing. You can provide these to createResolvers. It is recommended to use the graphql-scalars library for common types.
    import { createResolvers } from "./gqlkit/__generated__/resolvers.js";
    import { GraphQLDateTime } from "graphql-scalars";
    import { GraphQLScalarType, Kind } from "graphql";
    
    // Custom implementation example
    const CustomScalar = new GraphQLScalarType({
      name: "CustomScalar",
      serialize(value) { return value; },
      parseValue(value) { return value; },
      parseLiteral(ast) { return ast.value; },
    });
    
    const resolvers = createResolvers({
      scalars: {
        DateTime: GraphQLDateTime, // Using graphql-scalars
        Custom: CustomScalar,       // Using custom implementation
      },
    });
  12. Exclude TypeScript fields from the GraphQL schema

    To prevent internal TypeScript fields from being exposed in the public GraphQL API, use GqlObject with the ignoreFields option. Excluded fields will not appear in the schema and will not have resolvers generated.
    import { type GqlObject, type IDString } from "@gqlkit-ts/runtime";
    
    export type User = GqlObject<
      {
        id: IDString;
        name: string;
        email: string | null;
        internalId: string;  // Excluded
        cacheKey: string;    // Excluded
      },
      { ignoreFields: "internalId" | "cacheKey" }
    >;