---
url: /docs/schema/prisma.md
---
# Prisma

[Prisma ORM](https://www.prisma.io/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](../silk) from Prisma Schema.
* Use the resolver factory to quickly create CRUD operations from Prisma.

## Installation

Please refer to Prisma's [documentation](https://www.prisma.io/docs) to install Prisma and the corresponding database driver.

::: code-group

```sh [npm]
npm i graphql @gqloom/core @gqloom/prisma
```

```sh [pnpm]
pnpm add graphql @gqloom/core @gqloom/prisma
```

```sh [yarn]
yarn add graphql @gqloom/core @gqloom/prisma
```

```sh [bun]
bun add graphql @gqloom/core @gqloom/prisma
```

```sh [deno]
deno add npm:graphql npm:prisma npm:@gqloom/core npm:@gqloom/prisma
```

:::

## Configuration

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

```prisma title="schema.prisma"
generator client {
  provider = "prisma-client-js"
}

generator gqloom { // [!code hl]
  provider = "prisma-gqloom" // [!code hl]
} // [!code hl]

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:

| Parameter      | Description                                                                                                                | Default Value                           |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `gqloomPath`   | The path to the GQLoom package.                                                                                            | `@gqloom/prisma`                        |
| `clientOutput` | The path to the Prisma client.                                                                                             | `node_modules/@prisma/client`           |
| `output`       | The folder path where the generated files will be located.                                                                 | `node_modules/@gqloom/prisma/generated` |
| `commonjsFile` | The file name of the CommonJS file. Use an empty string `""` to skip generation of the CommonJS file.                      | `index.cjs`                             |
| `moduleFile`   | The file name of the ES module file. Use an empty string `""` to skip generation of the ES module file.                    | `index.js`                              |
| `typesFiles`   | The file name(s) of the TypeScript declaration file(s). Use `[]` to skip generation of the TypeScript declaration file(s). | `["index.d.ts"]`                        |

### Generate Silk

```sh
npx prisma generate
```

## Using Silk

After generating the silk, we can use it in the `resolver`. We use `useSelectedFields` to ensure only the fields required by the GraphQL query are selected:

```ts
import { resolver, query, field, weave } from '@gqloom/core'
import { asyncContextProvider } from '@gqloom/core/context'
import { useSelectedFields } from "@gqloom/prisma/context"
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({
        select: useSelectedFields(User),
        where: { id },
      })
    },
  }),

  posts: field(Post.list(), async (user) => {
    const posts = await db.user
      .findUnique({ where: { id: user.id } })
      .posts({ select: useSelectedFields(Post) })
    return posts ?? []
  }),
})

const postResolver = resolver.of(Post, {
  author: field(User.nullable())
    .derivedFrom("authorId")
    .resolve((post) => {
      if (!post.authorId) return null
      return db.user.findUnique({ where: { id: post.authorId } })
    }),
})

export const schema = weave(asyncContextProvider, ValibotWeaver, userResolver, postResolver)
```

As shown in the code above, we can directly use the types generated by Prisma within the `resolver`. Here, we've defined two resolvers: `userResolver` and `postResolver`.

In `userResolver`, we use `User` as the parent type for `resolver.of` and define two fields:

* The `user` query: The return type is `User.nullable()`, indicating it may return a single user or null. It accepts an `id` parameter and uses Prisma's `findUnique` method to query the database.
* The `posts` field: The return type is `Post.list()`, meaning it returns a list of all articles by the user. It fetches the user's articles through Prisma's relational queries.

In `postResolver`, we use `Post` as the parent type and define one field:

* The `author` field: The return type is `User`, representing the author of the article. It retrieves the author information through Prisma's relational queries.

All queries utilize the `useSelectedFields()` function to ensure that only the fields requested in the GraphQL query are selected. This helps optimize database query performance. This function requires [enabling context](../context). For runtimes where the `useSelectedFields()` function cannot be used, we can also use the `getSelectedFields()` function to obtain the columns that need to be selected for the current query.

### Derived Fields

Adding derived fields to the model is quite simple. However, it's important to use the `field().derivedFrom()` method to declare the columns on which it depends, so that the `useSelectedFields` method can correctly select these columns:

```ts
export const postResolver = resolver.of(Post, {
  abstract: field(v.string())
    .derivedFrom("title", "content")
    .resolve((post) => {
      return `${post.title} ${post.content?.slice(0, 60)}...`
    }),
})
```

### Hiding Fields

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

```ts
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, // [!code hl]
})
```

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

## Model Configuration

You can customize output fields, input behavior, and metadata for a specific Prisma model via the `.config()` method on the generated silk.

### Output Field Configuration

You can use the `fields` option to customize the GraphQL Object Type generated from the model. This lets you override field types, add descriptions, or hide specific fields.

```ts
import { User } from '@gqloom/prisma/generated'
import { weave, SYMBOLS } from '@gqloom/core'
import { GraphQLID } from 'graphql'

const userConfig = User.config({
  description: "System user information", // Add description to the GraphQL type
  fields: {
    // Override field description
    email: { description: "User's unique email address" },
    // Override field type; supports GraphQL type or silk
    id: { type: GraphQLID },
    // Hide field so it does not appear in query results
    password: SYMBOLS.FIELD_HIDDEN,
  },
})

export const schema = weave(userConfig, userResolver, postResolver)
```

### Input Field Behavior

You can use the `input` option to control how fields behave in various input types (e.g. `CreateInput`, `UpdateInput`, `WhereInput`). You can decide per "operation" whether a field is visible or override its input type.

Supported operation types:

* `create`: Input for create operations (e.g. `UserCreateInput`).
* `update`: Input for update operations (e.g. `UserUpdateInput`).
* `filters`: Input for filter operations (e.g. `UserWhereInput`).

```ts
import { User } from '@gqloom/prisma/generated'
import { weave } from '@gqloom/core'
import * as v from 'valibot'

const userConfig = User.config({
  input: {
    // Hide email in create
    email: { create: false },
    // Override name in update with a required string (via silk)
    name: { update: v.string() },
    // By default hide filter for all fields
    "*": { filters: false },
    // Only enable filter for id
    id: { filters: true },
  },
})

export const schema = weave(userConfig, userResolver, postResolver)
```

:::tip Priority
For input types, behavior defined in the `input` option has the highest priority; it overrides both `fields` config and global presets.
:::

## 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 input types for common operations. Using it can greatly reduce boilerplate, which is helpful for fast iteration.

```ts
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. `PrismaResolverFactory` accepts two arguments: the first is the model used as silk, the second is a `PrismaClient` instance.

### Relationship Fields

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

```ts
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) => { // [!code --]
    const posts = await db.user.findUnique({ where: { id: user.id } }).posts() // [!code --]
    return posts ?? [] // [!code --]
  }), // [!code --]
  posts: userResolverFactory.relationField('posts'), // [!code ++]
})

const postResolver = resolver.of(Post, {
  author: field(User, async (post) => { // [!code --]
    const author = await db.post.findUnique({ where: { id: post.id } }).author() // [!code --]
    return author! // [!code --]
  }), // [!code --]
  author: postResolverFactory.relationField('author'), // [!code ++]

  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 argument: the name of the relationship field.

### Queries

The resolver factory pre-defines common queries:

* countQuery
* findFirstQuery
* findManyQuery
* findUniqueQuery

You can use them directly:

```ts
const userResolver = resolver.of(User, {
  user: query(User.nullable(), { // [!code --]
    input: { id: v.number() }, // [!code --]
    resolve: ({ id }) => { // [!code --]
      return db.user.findUnique({ where: { id } }) // [!code --]
    }, // [!code --]
  }), // [!code --]
  user: userResolverFactory.findUniqueQuery(), // [!code ++]

  posts: userResolverFactory.relationField('posts'),
})
```

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

### Mutations

The resolver factory pre-defines common mutations:

* createMutation
* createManyMutation
* deleteMutation
* deleteManyMutation
* updateMutation
* updateManyMutation
* upsertMutation

You can use them directly:

```ts
const postResolver = resolver.of(Post, {
  createPost: postResolverFactory.createMutation(), // [!code hl]

  author: postResolverFactory.relationField('author'),

  authorId: field.hidden,
})
```

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

### Custom Input

The resolver factory’s pre-defined queries and mutations support custom input. You can define the input type via the `input` option:

```ts
import * as v from "valibot"

const userResolver = resolver.of(User, {
  user: userResolverFactory.findUniqueQuery().input(
    v.pipe( // [!code hl]
      v.object({ id: v.number() }), // [!code hl]
      v.transform(({ id }) => ({ where: { id } })) // [!code hl]
    ) // [!code hl]
  ),

  posts: userResolverFactory.relationField("posts"),
})
```

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

### Adding Middleware

The resolver factory’s pre-defined queries, mutations, and fields support middleware. You can define it via the `middlewares` option:

```ts
const postResolver = resolver.of(Post, {
  createPost: postResolverFactory.createMutation().use(async (next) => {
    const user = await useAuthedUser() // [!code hl]
    if (user == null) throw new GraphQLError("Please login first") // [!code hl]
    return next() // [!code hl]
  }), // [!code hl]

  author: postResolverFactory.relationField("author"),

  authorId: field.hidden,
})
```

In the above code, we use the `middlewares` option to define middleware. `async (next) => { ... }` is the middleware. `useAuthedUser()` is a custom function that returns the current user; if not logged in, it throws, otherwise we call `next()` to continue.

### Complete Resolver

You can create a full resolver directly from the resolver factory:

```ts
// Readonly Resolver
const userQueriesResolver = userResolverFactory.queriesResolver()

// Full Resolver
const userResolver = userResolverFactory.resolver()
```

There are two methods:

* `usersResolverFactory.queriesResolver()`: Creates a resolver with only queries and relation fields.
* `usersResolverFactory.resolver()`: Creates a resolver with all queries, mutations, and relation fields.

## Custom Type Mapping

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

First, use `PrismaWeaver.config` to define type mapping. Here we import `GraphQLDateTime` and `GraphQLJSON` from [graphql-scalars](https://the-guild.dev/graphql/scalars). When the types are `DateTime` or `Json`, we map them to the corresponding GraphQL scalars.

```ts twoslash
import { GraphQLDateTime, GraphQLJSON } from 'graphql-scalars'
import { PrismaWeaver } from '@gqloom/prisma'

export const prismaWeaverConfig = PrismaWeaver.config({
  /**
   * Emit @id fields as GraphQL ID type (output types only).
   * Default is true. Set to false to use the underlying scalar (e.g. Int or String).
   */
  emitIdAsIDType: false,

  presetGraphQLType: (type) => {
    switch (type) {
      case 'DateTime':
        return GraphQLDateTime
      case 'Json':
        return GraphQLJSON
    }
  },
})
```

Pass this config into the `weave` function when building the GraphQL schema:

```ts
import { weave } from "@gqloom/core"

export const schema = weave(prismaWeaverConfig, userResolver, postResolver)
```

## Default Type Mapping

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

| Prisma Type | GraphQL Type     |
| ----------- | ---------------- |
| Int @id     | `GraphQLID`      |
| String @id  | `GraphQLID`      |
| BigInt      | `GraphQLInt`     |
| Int         | `GraphQLInt`     |
| Decimal     | `GraphQLFloat`   |
| Float       | `GraphQLFloat`   |
| Boolean     | `GraphQLBoolean` |
| DateTime    | `GraphQLString`  |
| String      | `GraphQLString`  |
