GraphQL Loom
Build GraphQL server enjoyably and efficiently
The most familiar Schema Library
import { field, resolver, weave } from "@gqloom/core" import { ValibotWeaver } from "@gqloom/valibot" import * as v from "valibot" const Giraffe = v.object({ __typename: v.nullish(v.literal("Giraffe")), name: v.pipe(v.string(), v.description("The giraffe's name")), birthday: v.date(), }) const giraffeResolver = resolver.of(Giraffe, { age: field(v.pipe(v.number(), v.integer())) .input({ currentDate: v.pipe( v.nullish(v.string(), () => new Date().toISOString()), v.transform((x) => new Date(x)) ), }) .resolve((giraffe, { currentDate }) => { return currentDate.getFullYear() - giraffe.birthday.getFullYear() }), }) export const schema = weave(ValibotWeaver, giraffeResolver)import { field, resolver, weave } from "@gqloom/core" import { ZodWeaver } from "@gqloom/zod" import * as z from "zod" const Giraffe = z.object({ __typename: z.literal("Giraffe").nullish(), name: z.string().describe("The giraffe's name"), birthday: z.date(), }) const giraffeResolver = resolver.of(Giraffe, { age: field(z.number().int()) .input({ currentDate: z.coerce .date() .nullish() .transform((x) => x ?? new Date()), }) .resolve((giraffe, { currentDate }) => { return currentDate.getFullYear() - giraffe.birthday.getFullYear() }), }) export const schema = weave(ZodWeaver, giraffeResolver)import { field, resolver, weave } from "@gqloom/core" import { YupWeaver } from "@gqloom/yup" import { date, number, object, string } from "yup" const Giraffe = object({ name: string().required().meta({ description: "The giraffe's name" }), birthday: date().required(), }).label("Giraffe") const giraffeResolver = resolver.of(Giraffe, { age: field(number().integer().nonNullable()) .input({ currentDate: date().default(() => new Date()), }) .resolve((giraffe, { currentDate }) => { return currentDate.getFullYear() - giraffe.birthday.getFullYear() }), }) export const schema = weave(YupWeaver, giraffeResolver)import { field, resolver, weave } from "@gqloom/core" import { EffectWeaver } from "@gqloom/effect" import { Schema } from "effect" const standard = Schema.standardSchemaV1 const Giraffe = standard( Schema.Struct({ name: Schema.String.annotations({ description: "The giraffe's name" }), birthday: Schema.Date, }).annotations({ title: "Giraffe", }) ) const giraffeResolver = resolver.of(Giraffe, { age: field(standard(Schema.Number)) .input({ currentDate: standard(Schema.NullOr(Schema.String)), }) .resolve((giraffe, input) => { const currentDate = input.currentDate ? new Date(input.currentDate) : new Date() return currentDate.getFullYear() - giraffe.birthday.getFullYear() }), }) export const schema = weave(EffectWeaver, giraffeResolver)import { field, resolver, weave } from "@gqloom/core" import { jsonSilk } from "@gqloom/json" const Giraffe = jsonSilk({ title: "Giraffe", type: "object", properties: { name: { type: "string" }, birthday: { type: "string", format: "date-time" }, }, required: ["name", "birthday"], }) const helloResolver = resolver.of(Giraffe, { age: field(jsonSilk({ type: "integer" })) .input( jsonSilk({ type: "object", properties: { currentDate: { type: "string", format: "date-time", default: new Date().toISOString(), }, }, }) ) .resolve((giraffe, { currentDate }) => { return ( new Date(currentDate).getFullYear() - new Date(giraffe.birthday).getFullYear() ) }), }) export const schema = weave(helloResolver)type Giraffe { """The giraffe's name""" name: String! birthday: String! age(currentDate: String): Int! }- 🧩
Rich Integration
Use your most familiar validation libraries and ORMs to build your next GraphQL application.
- 🔒
Type Safety
Automatically infer types from the Schema, enjoy intelligent code completion during development, and detect potential problems during compilation.
- 🔋
Fully Prepared
Middleware, context, subscriptions, and federated graphs are ready.
- 🔮
No Magic
Without decorators, metadata, reflection, or code generation, it can run anywhere with just JavaScript/TypeScript.
- 🧑💻
Development Experience
Fewer boilerplate codes, semantic API design, and extensive ecosystem integration make development enjoyable.
Build Complete CRUD Interfaces Instantly
Create CRUD operations with predefined database models from MikroORM, Drizzle, Prisma by using ResolverFactory.
- Deep integration with multiple ORMs, using existing database table definitions as threads without needing to redefine GraphQL types;
- Build fully functional GraphQL interfaces in minutes: relational queries, create, delete, and update operations;
- Easily extend interfaces: freely modify input or output types for each interface, add custom middleware and logic;
- Seamless integration with various validation libraries, using your most familiar validation library to validate input data and extend interfaces;
import { createServer } from "node:http" import { weave } from "@gqloom/core" import { createMemoization } from "@gqloom/core/context" import { MikroResolverFactory } from "@gqloom/mikro-orm" import { MikroORM } from "@mikro-orm/libsql" import { createYoga } from "graphql-yoga" import { Post, User } from "src/entities" const ormPromise = MikroORM.init({ dbName: ":memory:", entities: [User, Post], }) const useEm = createMemoization(async () => (await ormPromise).em.fork()) const userResolver = new MikroResolverFactory(User, useEm).resolver() const postResolver = new MikroResolverFactory(Post, useEm).resolver() const schema = weave(userResolver, postResolver) const yoga = createYoga({ schema }) const server = createServer(yoga) server.listen(4000, () => { console.info("Server is running on http://localhost:4000/graphql") }) import { mikroSilk } from "@gqloom/mikro-orm" import { defineEntity, type InferEntity } from "@mikro-orm/core" const UserEntity = defineEntity({ name: "User", properties: (p) => ({ id: p.integer().primary().autoincrement(), createdAt: p.datetime().onCreate(() => new Date()), email: p.string(), name: p.string(), role: p.string().$type<"admin" | "user">().default("user"), posts: () => p.oneToMany(PostEntity).mappedBy("author"), }), }) export interface IUser extends InferEntity<typeof UserEntity> {} const PostEntity = defineEntity({ name: "Post", properties: (p) => ({ id: p.integer().primary().autoincrement(), createdAt: p.datetime().onCreate(() => new Date()), updatedAt: p .datetime() .onCreate(() => new Date()) .onUpdate(() => new Date()), published: p.boolean().default(false), title: p.string(), author: () => p.manyToOne(UserEntity), }), }) export interface IPost extends InferEntity<typeof PostEntity> {} export const User = mikroSilk(UserEntity) export const Post = mikroSilk(PostEntity)input BooleanComparisonOperators { """ <@ """ contained: [Boolean!] """ @> """ contains: [Boolean!] """ Equals. Matches values that are equal to a specified value. """ eq: Boolean """ Greater. Matches values that are greater than a specified value. """ gt: Boolean """ Greater or Equal. Matches values that are greater than or equal to a specified value. """ gte: Boolean """ Contains, Contains, Matches any of the values specified in an array. """ in: [Boolean!] """ Lower, Matches values that are less than a specified value. """ lt: Boolean """ Lower or equal, Matches values that are less than or equal to a specified value. """ lte: Boolean """ Not equal. Matches all values that are not equal to a specified value. """ ne: Boolean """ Not contains. Matches none of the values specified in an array. """ nin: [Boolean!] """ && """ overlap: [Boolean!] } input IDComparisonOperators { """ <@ """ contained: [ID!] """ @> """ contains: [ID!] """ Equals. Matches values that are equal to a specified value. """ eq: ID """ Greater. Matches values that are greater than a specified value. """ gt: ID """ Greater or Equal. Matches values that are greater than or equal to a specified value. """ gte: ID """ Contains, Contains, Matches any of the values specified in an array. """ in: [ID!] """ Lower, Matches values that are less than a specified value. """ lt: ID """ Lower or equal, Matches values that are less than or equal to a specified value. """ lte: ID """ Not equal. Matches all values that are not equal to a specified value. """ ne: ID """ Not contains. Matches none of the values specified in an array. """ nin: [ID!] """ && """ overlap: [ID!] } enum MikroOnConflictAction { ignore merge } type Mutation { createPost(data: PostRequiredInput!): Post! createUser(data: UserRequiredInput!): User! deletePost(where: PostFilter): Int! deleteUser(where: UserFilter): Int! insertManyPost(data: [PostRequiredInput]!): [Post!]! insertManyUser(data: [UserRequiredInput]!): [User!]! insertPost(data: PostRequiredInput!): Post! insertUser(data: UserRequiredInput!): User! updatePost(data: PostPartialInput!, where: PostFilter): Int! updateUser(data: UserPartialInput!, where: UserFilter): Int! upsertManyPost( data: [PostPartialInput!]! onConflictAction: MikroOnConflictAction onConflictExcludeFields: [String!] onConflictFields: [String!] onConflictMergeFields: [String!] ): [Post!]! upsertManyUser( data: [UserPartialInput!]! onConflictAction: MikroOnConflictAction onConflictExcludeFields: [String!] onConflictFields: [String!] onConflictMergeFields: [String!] ): [User!]! upsertPost( data: PostPartialInput! onConflictAction: MikroOnConflictAction onConflictExcludeFields: [String!] onConflictFields: [String!] onConflictMergeFields: [String!] ): Post! upsertUser( data: UserPartialInput! onConflictAction: MikroOnConflictAction onConflictExcludeFields: [String!] onConflictFields: [String!] onConflictMergeFields: [String!] ): User! } type Post { author: User createdAt: String! id: ID! published: Boolean! title: String! updatedAt: String! } type PostCursor { endCursor: String hasNextPage: Boolean! hasPrevPage: Boolean! items: [Post!]! length: Int startCursor: String totalCount: Int! } input PostFilter { """ Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. """ AND: [PostFilter!] """ Inverts the effect of a query expression and returns documents that do not match the query expression. """ NOT: PostFilter """ Joins query clauses with a logical OR returns all documents that match the conditions of either clause. """ OR: [PostFilter!] createdAt: StringComparisonOperators id: IDComparisonOperators published: BooleanComparisonOperators title: StringComparisonOperators updatedAt: StringComparisonOperators } input PostOrderBy { createdAt: QueryOrder id: QueryOrder published: QueryOrder title: QueryOrder updatedAt: QueryOrder } input PostPartialInput { author: ID createdAt: String id: ID published: Boolean title: String updatedAt: String } input PostRequiredInput { author: ID! createdAt: String id: ID published: Boolean title: String! updatedAt: String } type Query { countPost(where: PostFilter): Int! countUser(where: UserFilter): Int! findOnePost(offset: Int, orderBy: PostOrderBy, where: PostFilter!): Post findOnePostOrFail( offset: Int orderBy: PostOrderBy where: PostFilter! ): Post! findOneUser(offset: Int, orderBy: UserOrderBy, where: UserFilter!): User findOneUserOrFail( offset: Int orderBy: UserOrderBy where: UserFilter! ): User! findPost( limit: Int offset: Int orderBy: PostOrderBy where: PostFilter ): [Post!]! findPostByCursor( after: String before: String first: Int last: Int orderBy: PostOrderBy where: PostFilter ): PostCursor findUser( limit: Int offset: Int orderBy: UserOrderBy where: UserFilter ): [User!]! findUserByCursor( after: String before: String first: Int last: Int orderBy: UserOrderBy where: UserFilter ): UserCursor } enum QueryOrder { ASC ASC_NULLS_FIRST ASC_NULLS_LAST DESC DESC_NULLS_FIRST DESC_NULLS_LAST } input StringComparisonOperators { """ <@ """ contained: [String!] """ @> """ contains: [String!] """ Equals. Matches values that are equal to a specified value. """ eq: String """ Full text. A driver specific full text search function. """ fulltext: String """ Greater. Matches values that are greater than a specified value. """ gt: String """ Greater or Equal. Matches values that are greater than or equal to a specified value. """ gte: String """ ilike """ ilike: String """ Contains, Contains, Matches any of the values specified in an array. """ in: [String!] """ Like. Uses LIKE operator """ like: String """ Lower, Matches values that are less than a specified value. """ lt: String """ Lower or equal, Matches values that are less than or equal to a specified value. """ lte: String """ Not equal. Matches all values that are not equal to a specified value. """ ne: String """ Not contains. Matches none of the values specified in an array. """ nin: [String!] """ && """ overlap: [String!] """ Regexp. Uses REGEXP operator """ re: String } type User { createdAt: String! email: String! id: ID! name: String! posts(where: PostFilter): [Post!]! role: String! } type UserCursor { endCursor: String hasNextPage: Boolean! hasPrevPage: Boolean! items: [User!]! length: Int startCursor: String totalCount: Int! } input UserFilter { """ Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. """ AND: [UserFilter!] """ Inverts the effect of a query expression and returns documents that do not match the query expression. """ NOT: UserFilter """ Joins query clauses with a logical OR returns all documents that match the conditions of either clause. """ OR: [UserFilter!] createdAt: StringComparisonOperators email: StringComparisonOperators id: IDComparisonOperators name: StringComparisonOperators role: StringComparisonOperators } input UserOrderBy { createdAt: QueryOrder email: QueryOrder id: QueryOrder name: QueryOrder role: QueryOrder } input UserPartialInput { createdAt: String email: String id: ID name: String posts: [ID] role: String } input UserRequiredInput { createdAt: String email: String! id: ID name: String! posts: [ID] role: String }Full Featured GraphQL
Resolver
Resolvers are the core components of GraphQL. You can define query, mutation, and subscription operations within them, and also dynamically add additional fields to objects for flexible data processing.
Context
With the context mechanism, you can conveniently inject data anywhere in the application, ensuring efficient data flow between different components and layers.
Middleware
Adopting the concept of aspect - oriented programming, middleware allows you to seamlessly integrate additional logic during the resolution process, such as error handling, user permission verification, and log tracking, enhancing the robustness and maintainability of the system.
Dataloader
Dataloader is a powerful tool for optimizing performance. It can fetch data in batches, significantly reducing the number of database queries, effectively improving system performance, and making the code structure clearer and easier to maintain.
Subscription
The subscription feature provides clients with the ability to obtain real - time data updates without manual polling, ensuring that clients always stay in sync with server data and enhancing the user experience.
Federation
Federation is a microservice - based GraphQL architecture that can easily aggregate multiple services to enable cross - service queries, allowing you to manage complex distributed systems as if operating on a single graph.
Full Power of GraphQL
- 🔐
Type Safety
Strong type system to ensure the consistency and security of data from the server to the client.
- 🧩
Flexible Aggregation
Automatically aggregate multiple queries, reducing the number of client requests and ensuring the simplicity of the server-side API.
- 🚀
Efficient Querying
The client can specify the required data structure, reducing unnecessary data transfer and improving the performance and maintainability of the API.
- 🔌
Easy to Extend
Extending the API by adding new fields and types without modifying existing code.
- 👥
Efficient Collaboration
Using Schema as documentation, which can reduce communication costs and improve development efficiency in team development.
- 🌳
Thriving Ecosystem
Tools and frameworks are emerging constantly. The active community, with diverse applications, is growing fast and has bright prospects.