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 { 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.