GraphQL Loom
愉快且高效地建构 GraphQL 服务
最为熟知的类型库
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!
}- 🧩
丰富集成
使用你最熟悉的验证库和 ORM 来建构你的下一个 GraphQL 应用;
- 🔒
类型安全
从 Schema 自动推导类型,在开发时享受智能提示,在编译时发现潜在问题;
- 🔋
整装待发
中间件、上下文、订阅、联邦图已经准备就绪;
- 🔮
抛却魔法
没有装饰器、没有元数据和反射、没有代码生成,只需要 JavaScript/TypeScript 就可以在任何地方运行;
- 🧑💻
开发体验
更少的样板代码、语义化的 API 设计、广泛的生态集成使开发愉快;
即刻建构完整的增删改查接口
通过 ResolverFactory 使用在MikroORM、Drizzle、Prisma 已定义的数据库模型创建 CRUD 操作。
- 深度集成多种 ORM,使用已经存在的数据库表定义作为丝线,不需要重复定义 GraphQL 类型;
- 在几分钟内建构内建构全功能的 GraphQL 接口:关系查询、增加、删除、更新操作;
- 轻松扩展接口:随意修改各个接口的输入或输出类型、添加自定义的中间件和逻辑;
- 与各种验证库无缝集成,使用最熟悉的验证库来验证输入数据并扩展接口;
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
}全功能 GraphQL
解析器(Resolver)
解析器是 GQLoom 的核心组件,你可以在其中定义查询、变更和订阅操作,还能为对象动态添加额外字段,实现灵活的数据处理。
上下文(Context)
借助上下文机制,你能够在应用程序的任意位置便捷地进行数据注入,确保数据在不同组件和层次间高效流通。
中间件(Middleware)
采用面向切面编程的思想,中间件允许你在解析过程中无缝嵌入额外逻辑,如错误捕获、用户权限校验和日志追踪,增强系统的健壮性和可维护性。
数据加载器(Dataloader)
数据加载器是优化性能的利器,它能够批量获取数据,显著减少数据库的查询次数,有效提升系统性能,同时让代码结构更加清晰,易于维护。
订阅(Subscription)
订阅功能为客户端提供了实时获取数据更新的能力,无需手动轮询,确保客户端始终与服务器数据保持同步,提升用户体验。
联邦图(Federation)
联邦图是一种微服务化的 GraphQL 架构,它能够轻松聚合多个服务,实现跨服务查询,让你可以像操作单个图一样管理复杂的分布式系统。
GraphQL 的磅礴之力
- 🔐
类型安全
强类型查询语言,可以确保从服务端到客户端数据的一致性和安全性。
- 🧩
灵活聚合
自动聚合多个查询,既减少客户端的请求次数,也保证服务端 API 的简洁性。
- 🚀
高效查询
客户端可以指定所需的数据结构,从而减少不必要的数据传输,提高 API 的性能和可维护性。
- 🔌
易于扩展
通过添加新的字段和类型来扩展 API,而不需要修改现有的代码。
- 👥
高效协作
使用 Schema 作为文档,减少沟通成本,提高开发效率。
- 🌳
繁荣生态
各类工具与框架不断推陈出新,社区活跃且发展迅速,应用领域广泛且未来前景广阔。