Skip to content
GQLoom

GraphQL Loom

愉快且高效地建构 GraphQL 服务

GQLoom Logo

最为熟知的类型库

ts
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
)
ts
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
)
ts
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
)
ts
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
)
GraphQL
type Giraffe {
  """The giraffe's name"""
  name: String!
  birthday: String!
  age(currentDate: String): Int!
}
  • 🧩

    丰富集成

    使用你最熟悉的验证库和 ORM 来建构你的下一个 GraphQL 应用;

  • 🔒

    类型安全

    从 Schema 自动推导类型,在开发时享受智能提示,在编译时发现潜在问题;

  • 🔋

    整装待发

    中间件、上下文、订阅、联邦图已经准备就绪;

  • 🔮

    抛却魔法

    没有装饰器、没有元数据和反射、没有代码生成,只需要 JavaScript/TypeScript 就可以在任何地方运行;

  • 🧑‍💻

    开发体验

    更少的样板代码、语义化的 API 设计、广泛的生态集成使开发愉快;

即刻建构完整的增删改查接口

通过 ResolverFactory 使用在MikroORMDrizzlePrisma 已定义的数据库模型创建 CRUD 操作。

  • 深度集成多种 ORM,使用已经存在的数据库表定义作为丝线,不需要重复定义 GraphQL 类型;
  • 在几分钟内建构内建构全功能的 GraphQL 接口:关系查询、增加、删除、更新操作;
  • 轻松扩展接口:随意修改各个接口的输入或输出类型、添加自定义的中间件和逻辑;
  • 与各种验证库无缝集成,使用最熟悉的验证库来验证输入数据并扩展接口;
ts
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")
})
ts
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
)
GraphQL
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

GraphQL 的磅礴之力

  • 🔐

    类型安全

    强类型查询语言,可以确保从服务端到客户端数据的一致性和安全性。

  • 🧩

    灵活聚合

    自动聚合多个查询,既减少客户端的请求次数,也保证服务端 API 的简洁性。

  • 🚀

    高效查询

    客户端可以指定所需的数据结构,从而减少不必要的数据传输,提高 API 的性能和可维护性。

  • 🔌

    易于扩展

    通过添加新的字段和类型来扩展 API,而不需要修改现有的代码。

  • 👥

    高效协作

    使用 Schema 作为文档,减少沟通成本,提高开发效率。

  • 🌳

    繁荣生态

    各类工具与框架不断推陈出新,社区活跃且发展迅速,应用领域广泛且未来前景广阔。