Overview of gqlkit core concepts and design
graphql-tools via makeExecutableSchema.website·Indexed 31 days ago
https://gqlkit.izumin.devA 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.
graphql-tools via makeExecutableSchema.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 postgressrc/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}`;
});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"]
>;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);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;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)
);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[]>(() => []);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");
});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
);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
},
});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" }
>;