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 { 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!
}
- 🧩
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.
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.
CRUD interfaces are ready for activation
Create CRUD operations with predefined database models from Drizzle, MikroORM, Prisma by using ResolverFactory.
- Like a skilled weaver, embed precisely defined database tables seamlessly into the GraphQL Schema.
- With just a few lines of code, easily build a CRUD system and enjoy ORM's convenience.
- Both resolvers and single operations can be customized with inputs and middleware to meet diverse needs.
- Using a flexible approach, freely combine resolvers and add operations to the graph for endless potential.
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
}
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.