GQLoom

Prisma

Prisma ORM offers developers a brand - new experience when working with databases, thanks to its intuitive data models, automatic migrations, type safety, and auto - completion features.

@gqloom/prisma provides the integration of GQLoom and Prisma:

  • Generate silk from Prisma Schema.
  • Use the resolver factory to quickly create CRUD operations from Prisma.

Installation

npm i -D prisma
npm i @gqloom/core @gqloom/prisma

You can find more information about installation in the Prisma documentation.

Configuration

Define your Prisma Schema in the prisma/schema.prisma file:

generator client {
  provider = "prisma-client-js"
}
 
generator gqloom { 
  provider = "prisma-gqloom"
} 
 
datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}
 
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}
 
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

Generator Parameters

The generator accepts the following parameters:

ParameterDescriptionDefault Value
gqloomPathThe path to the GQLoom package.@gqloom/prisma
clientOutputThe path to the Prisma client.node_modules/@prisma/client
outputThe folder path where the generated files will be located.node_modules/@gqloom/prisma/generated
commonjsFileThe file name of the CommonJS file. Use an empty string "" to skip generation of the CommonJS file.index.cjs
moduleFileThe file name of the ES module file. Use an empty string "" to skip generation of the ES module file.index.js
typesFilesThe file name(s) of the TypeScript declaration file(s). Use [] to skip generation of the TypeScript declaration file(s).["index.d.ts"]

Generate Silk

npx prisma generate

Using Silk

After generating the silk, we can use it in the resolver:

import { resolver, query, field, weave } from '@gqloom/core'
import { ValibotWeaver } from '@gqloom/valibot'
import { Post, User } from '@gqloom/prisma/generated'
import * as v from 'valibot'
import { PrismaClient } from '@prisma/client'
 
const db = new PrismaClient({})
 
const userResolver = resolver.of(User, {
  user: query(User.nullable(), {
    input: { id: v.number() },
    resolve: ({ id }) => {
      return db.user.findUnique({ where: { id } })
    },
  }),
 
  posts: field(Post.list(), async (user) => {
    const posts = await db.user.findUnique({ where: { id: user.id } }).posts()
    return posts ?? []
  }),
})
 
const postResolver = resolver.of(Post, {
  author: field(User, async (post) => {
    const author = await db.post.findUnique({ where: { id: post.id } }).author()
    return author!
  }),
})
 
export const schema = weave(ValibotWeaver, userResolver, postResolver)

Hiding Fields

@gqloom/prisma exposes all fields by default. If you want to hide certain fields, you can use field.hidden:

const postResolver = resolver.of(Post, {
  author: field(User, async (post) => {
    const author = await db.post.findUnique({ where: { id: post.id } }).author()
    return author!
  }),
 
  authorId: field.hidden, 
})

In the above code, we hide the authorId field, which means it will not appear in the generated GraphQL Schema.

Resolver Factory

@gqloom/prisma provides the PrismaResolverFactory to help you create resolver factories. With the resolver factory, you can quickly define common queries, mutations, and fields. The resolver factory also pre - defines the input types for common operation inputs. Using the resolver factory can significantly reduce boilerplate code, which is very useful for rapid development.

import { Post, User } from '@gqloom/prisma/generated'
import { PrismaResolverFactory } from '@gqloom/prisma'
 
import { PrismaClient } from '@prisma/client'
 
const db = new PrismaClient({})
 
const userResolverFactory = new PrismaResolverFactory(User, db)
const postResolverFactory = new PrismaResolverFactory(Post, db)

In the above code, we create resolver factories for the User and Post models. The PrismaResolverFactory accepts two parameters. The first is the model used as silk, and the second is an instance of PrismaClient.

Relationship Fields

The resolver factory provides the relationField method to define relationship fields:

const userResolver = resolver.of(User, {
  user: query(User.nullable(), {
    input: { id: v.number() },
    resolve: ({ id }) => {
      return db.user.findUnique({ where: { id } })
    },
  }),
 
  posts: field(Post.list(), async (user) => { 
    const posts = await db.user.findUnique({ where: { id: user.id } }).posts() 
    return posts ?? [] 
  }), 
  posts: userResolverFactory.relationField('posts'), 
})
 
const postResolver = resolver.of(Post, {
  author: field(User, async (post) => { 
    const author = await db.post.findUnique({ where: { id: post.id } }).author() 
    return author!
  }), 
  author: postResolverFactory.relationField('author'), 
 
  authorId: field.hidden,
})

In the above code, we use userResolverFactory.relationField('posts') and postResolverFactory.relationField('author') to define relationship fields. The relationField method accepts a string parameter representing the name of the relationship field.

Queries

The resolver factory pre - defines common queries:

  • countQuery
  • findFirstQuery
  • findManyQuery
  • findUniqueQuery

You can use them directly:

const userResolver = resolver.of(User, {
  user: query(User.nullable(), { 
    input: { id: v.number() }, 
    resolve: ({ id }) => { 
      return db.user.findUnique({ where: { id } }) 
    }, 
  }), 
  user: userResolverFactory.findUniqueQuery(), 
 
  posts: userResolverFactory.relationField('posts'),
})

In the above code, we use userResolverFactory.findUniqueQuery() to define the user query. The resolver factory will automatically create the input type and the resolver function.

Mutations

The resolver factory pre - defines common mutations:

  • createMutation
  • createManyMutation
  • deleteMutation
  • deleteManyMutation
  • updateMutation
  • updateManyMutation
  • upsertMutation

You can use them directly:

const postResolver = resolver.of(Post, {
  createPost: postResolverFactory.createMutation(), 
 
  author: postResolverFactory.relationField('author'),
 
  authorId: field.hidden,
})

In the above code, we use postResolverFactory.createMutation() to define the createPost mutation. The factory will automatically create the input type and the resolver function.

Custom Input

The pre-defined queries and mutations of the resolver factory support custom input. You can define the input type through the input option:

import * as v from 'valibot'
 
const userResolver = resolver.of(User, {
  user: userResolverFactory.findUniqueQuery({
    input: v.pipe( 
      v.object({ id: v.number() }), 
      v.transform(({ id }) => ({ where: { id } })) 
    ), 
  }),
 
  posts: userResolverFactory.relationField("posts"),
})

In the above code, we use valibot to define the input type. v.object({ id: v.number() }) defines the type of the input object, and v.transform(({ id }) => ({ where: { id } })) converts the input parameters into Prisma query parameters.

Adding Middleware

The pre-defined queries, mutations, and fields of the resolver factory support adding middleware. You can define middleware through the middlewares option:

const postResolver = resolver.of(Post, {
  createPost: postResolverFactory.createMutation({
    middlewares: [ 
      async (next) => { 
        const user = await useAuthedUser() 
        if (user == null) throw new GraphQLError('Please login first') 
        return next() 
      }, 
    ], 
  }),
 
  author: postResolverFactory.relationField('author'),
 
  authorId: field.hidden,
})

In the above code, we use the middlewares option to define middleware. async (next) => { ... } defines a middleware. useAuthedUser() is a custom function used to get the currently logged - in user. If the user is not logged in, an error is thrown; otherwise, next() is called to continue execution.

Creating a Resolver

You can directly create a Resolver from the resolver factory:

const userResolver = userResolverFactory.resolver()

In the above code, we use userResolverFactory.resolver() to create a Resolver. This Resolver will include all queries, mutations, and fields in the resolver factory.

Custom Type Mapping

To adapt to more Prisma types, we can extend GQLoom to add more type mappings.

First, we use PrismaWeaver.config to define the configuration of type mapping. Here we import GraphQLDateTime from [graphql - scalars](https://the - guild.dev/graphql/scalars). When encountering the DateTime type, we map it to the corresponding GraphQL scalar.

import {  } from 'graphql-scalars'
import {  } from '@gqloom/prisma'
 
export const  = .({
  : () => {
    switch () {
      case 'DateTime':
        return 
    }
  },
})

Pass the configuration to the weave function when weaving the GraphQL Schema:

import { weave } from "@gqloom/core"
 
export const schema = weave(prismaWeaverConfig, userResolver, postResolver)

Default Type Mapping

The following table lists the default mapping relationships between Prisma types and GraphQL types in GQLoom:

Prisma TypeGraphQL Type
Int @idGraphQLID
String @idGraphQLID
BigIntGraphQLInt
IntGraphQLInt
DecimalGraphQLFloat
FloatGraphQLFloat
BooleanGraphQLBoolean
DateTimeGraphQLString
StringGraphQLString

On this page