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 { 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 { yupSilk } from "@gqloom/yup"
import { date, number, object, string } from "yup"
const Giraffe = yupSilk(
object({
name: string().required().meta({ description: "The giraffe's name" }),
birthday: date().required(),
}).label("Giraffe")
)
const giraffeResolver = resolver.of(Giraffe, {
age: field(yupSilk(number().integer().nonNullable()))
.input({
currentDate: yupSilk(date().default(() => new Date())),
})
.resolve((giraffe, { currentDate }) => {
return currentDate.getFullYear() - giraffe.birthday.getFullYear()
}),
})
export const schema = weave(giraffeResolver)
type Giraffe {
"""The giraffe's name"""
name: String!
birthday: String!
age(currentDate: String): Int!
}
- 🧩
丰富集成
使用你最熟悉的验证库和 ORM 来建构你的下一个 GraphQL 应用;
- 🔒
类型安全
从 Schema 自动推导类型,在开发时享受智能提示,在编译时发现潜在问题;
- 🔋
整装待发
中间件、上下文、订阅、联邦图已经准备就绪;
- 🔮
抛却魔法
没有装饰器、没有元数据和反射、没有代码生成,只需要 JavaScript/TypeScript 就可以在任何地方运行;
- 🧑💻
开发体验
更少的样板代码、语义化的 API 设计、广泛的生态集成使开发愉快;
全功能 GraphQL
解析器(Resolver)
解析器是 GQLoom 的核心组件,你可以在其中定义查询、变更和订阅操作,还能为对象动态添加额外字段,实现灵活的数据处理。
上下文(Context)
借助上下文机制,你能够在应用程序的任意位置便捷地进行数据注入,确保数据在不同组件和层次间高效流通。
中间件(Middleware)
采用面向切面编程的思想,中间件允许你在解析过程中无缝嵌入额外逻辑,如错误捕获、用户权限校验和日志追踪,增强系统的健壮性和可维护性。
数据加载器(Dataloader)
数据加载器是优化性能的利器,它能够批量获取数据,显著减少数据库的查询次数,有效提升系统性能,同时让代码结构更加清晰,易于维护。
订阅(Subscription)
订阅功能为客户端提供了实时获取数据更新的能力,无需手动轮询,确保客户端始终与服务器数据保持同步,提升用户体验。
联邦图(Federation)
联邦图是一种微服务化的 GraphQL 架构,它能够轻松聚合多个服务,实现跨服务查询,让你可以像操作单个图一样管理复杂的分布式系统。
增删改查接口已就绪
通过 ResolverFactory 使用在Drizzle、MikroORM、Prisma 已定义的数据库模型创建 CRUD 操作。
- 恰似以精巧技艺织就锦章,将精准定义的数据库表格毫无瑕疵地嵌入 GraphQL Schema 架构体系,达成数据库表格与接口之间的无缝对接。
- 仅需编写少量代码,即可从数据库表格出发,举重若轻地搭建起增删改查操作体系,全方位沉浸于对象关系映射(ORM)技术所赋予的便捷体验之中。
- 不光是解析器能够灵活塑造,即便是单一操作,也可通过巧妙融入输入项与中间件,达成独具匠心的定制效果,精准贴合多样化需求。
- 凭借高度灵活的构建策略,游刃余地对解析器进行拼接组合,毫无阻碍地在数据图中植入各类操作,充分挖掘并拓展无限可能。
import { createServer } from "node:http"
import { weave } from "@gqloom/core"
import { drizzleResolverFactory } from "@gqloom/drizzle"
import { drizzle } from "drizzle-orm/node-postgres"
import { createYoga } from "graphql-yoga"
import * as tables from "src/schema"
const db = drizzle(process.env.DATABASE_URL!, { schema: tables })
const userResolver = drizzleResolverFactory(db, tables.users).resolver()
const postResolver = drizzleResolverFactory(db, tables.posts).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 { drizzleSilk } from "@gqloom/drizzle"
import { relations } from "drizzle-orm"
import * as t from "drizzle-orm/pg-core"
export const roleEnum = t.pgEnum("role", ["user", "admin"])
export const users = drizzleSilk(
t.pgTable("users", {
id: t.serial().primaryKey(),
createdAt: t.timestamp().defaultNow(),
email: t.text().unique().notNull(),
name: t.text(),
role: roleEnum().default("user"),
})
)
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const posts = drizzleSilk(
t.pgTable("posts", {
id: t.serial().primaryKey(),
createdAt: t.timestamp().defaultNow(),
updatedAt: t
.timestamp()
.defaultNow()
.$onUpdateFn(() => new Date()),
published: t.boolean().default(false),
title: t.varchar({ length: 255 }).notNull(),
authorId: t.integer(),
})
)
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}))
type UsersItem {
id: Int!
createdAt: String
email: String!
name: String
role: String
posts: [PostsItem!]!
}
type PostsItem {
id: Int!
createdAt: String
updatedAt: String
published: Boolean
title: String!
authorId: Int
author: UsersItem
}
type Query {
users(
offset: Int
limit: Int
orderBy: [UsersOrderBy!]
where: UsersFilters
): [UsersItem!]!
usersSingle(
offset: Int
orderBy: [UsersOrderBy!]
where: UsersFilters
): UsersItem
posts(
offset: Int
limit: Int
orderBy: [PostsOrderBy!]
where: PostsFilters
): [PostsItem!]!
postsSingle(
offset: Int
orderBy: [PostsOrderBy!]
where: PostsFilters
): PostsItem
}
input UsersOrderBy {
id: OrderDirection
createdAt: OrderDirection
email: OrderDirection
name: OrderDirection
role: OrderDirection
}
enum OrderDirection {
asc
desc
}
input UsersFilters {
id: PgSerialFilters
createdAt: PgTimestampFilters
email: PgTextFilters
name: PgTextFilters
role: PgEnumColumnFilters
OR: [UsersFiltersOr!]
}
input PgSerialFilters {
eq: Int
ne: Int
lt: Int
lte: Int
gt: Int
gte: Int
inArray: [Int!]
notInArray: [Int!]
isNull: Boolean
isNotNull: Boolean
OR: [PgSerialFiltersOr!]
}
input PgSerialFiltersOr {
eq: Int
ne: Int
lt: Int
lte: Int
gt: Int
gte: Int
inArray: [Int!]
notInArray: [Int!]
isNull: Boolean
isNotNull: Boolean
}
input PgTimestampFilters {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
OR: [PgTimestampFiltersOr!]
}
input PgTimestampFiltersOr {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
}
input PgTextFilters {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
OR: [PgTextFiltersOr!]
}
input PgTextFiltersOr {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
}
input PgEnumColumnFilters {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
OR: [PgEnumColumnFiltersOr!]
}
input PgEnumColumnFiltersOr {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
}
input UsersFiltersOr {
id: PgSerialFilters
createdAt: PgTimestampFilters
email: PgTextFilters
name: PgTextFilters
role: PgEnumColumnFilters
}
input PostsOrderBy {
id: OrderDirection
createdAt: OrderDirection
updatedAt: OrderDirection
published: OrderDirection
title: OrderDirection
authorId: OrderDirection
}
input PostsFilters {
id: PgSerialFilters
createdAt: PgTimestampFilters
updatedAt: PgTimestampFilters
published: PgBooleanFilters
title: PgVarcharFilters
authorId: PgIntegerFilters
OR: [PostsFiltersOr!]
}
input PgBooleanFilters {
eq: Boolean
ne: Boolean
lt: Boolean
lte: Boolean
gt: Boolean
gte: Boolean
inArray: [Boolean!]
notInArray: [Boolean!]
isNull: Boolean
isNotNull: Boolean
OR: [PgBooleanFiltersOr!]
}
input PgBooleanFiltersOr {
eq: Boolean
ne: Boolean
lt: Boolean
lte: Boolean
gt: Boolean
gte: Boolean
inArray: [Boolean!]
notInArray: [Boolean!]
isNull: Boolean
isNotNull: Boolean
}
input PgVarcharFilters {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
OR: [PgVarcharFiltersOr!]
}
input PgVarcharFiltersOr {
eq: String
ne: String
lt: String
lte: String
gt: String
gte: String
like: String
notLike: String
ilike: String
notIlike: String
inArray: [String!]
notInArray: [String!]
isNull: Boolean
isNotNull: Boolean
}
input PgIntegerFilters {
eq: Int
ne: Int
lt: Int
lte: Int
gt: Int
gte: Int
inArray: [Int!]
notInArray: [Int!]
isNull: Boolean
isNotNull: Boolean
OR: [PgIntegerFiltersOr!]
}
input PgIntegerFiltersOr {
eq: Int
ne: Int
lt: Int
lte: Int
gt: Int
gte: Int
inArray: [Int!]
notInArray: [Int!]
isNull: Boolean
isNotNull: Boolean
}
input PostsFiltersOr {
id: PgSerialFilters
createdAt: PgTimestampFilters
updatedAt: PgTimestampFilters
published: PgBooleanFilters
title: PgVarcharFilters
authorId: PgIntegerFilters
}
type Mutation {
insertIntoUsers(values: [UsersInsertInput!]!): [UsersItem!]!
insertIntoUsersSingle(value: UsersInsertInput!): UsersItem
updateUsers(where: UsersFilters, set: UsersUpdateInput!): [UsersItem!]!
deleteFromUsers(where: UsersFilters): [UsersItem!]!
insertIntoPosts(values: [PostsInsertInput!]!): [PostsItem!]!
insertIntoPostsSingle(value: PostsInsertInput!): PostsItem
updatePosts(where: PostsFilters, set: PostsUpdateInput!): [PostsItem!]!
deleteFromPosts(where: PostsFilters): [PostsItem!]!
}
input UsersInsertInput {
id: Int
createdAt: String
email: String!
name: String
role: String
}
input UsersUpdateInput {
id: Int
createdAt: String
email: String
name: String
role: String
}
input PostsInsertInput {
id: Int
createdAt: String
updatedAt: String
published: Boolean
title: String!
authorId: Int
}
input PostsUpdateInput {
id: Int
createdAt: String
updatedAt: String
published: Boolean
title: String
authorId: Int
}
GraphQL 的磅礴之力
- 🔐
类型安全
强类型查询语言,可以确保从服务端到客户端数据的一致性和安全性。
- 🧩
灵活聚合
自动聚合多个查询,既减少客户端的请求次数,也保证服务端 API 的简洁性。
- 🚀
高效查询
客户端可以指定所需的数据结构,从而减少不必要的数据传输,提高 API 的性能和可维护性。
- 🔌
易于扩展
通过添加新的字段和类型来扩展 API,而不需要修改现有的代码。
- 👥
高效协作
使用 Schema 作为文档,减少沟通成本,提高开发效率。
- 🌳
繁荣生态
各类工具与框架不断推陈出新,社区活跃且发展迅速,应用领域广泛且未来前景广阔。