PluginsPrisma Next

Prisma Next plugin

Experimental. prisma-next isn't on npm yet. The plugin is private: true in the Pothos workspace and tracks prisma-next from a sibling clone.

This plugin provides tighter integration with prisma-next, the new fluent collection-based ORM client. It makes it easier to define GraphQL types backed by your contract, helps solve N+1 queries for relations, and ships Relay integrations for nodes and connections.

This is a separate package from @pothos/plugin-prisma, which targets the existing Prisma client. The two are mutually exclusive.

Features

  • 🎨 Quickly define GraphQL types based on your prisma-next contract
  • 🦺 Strong type-safety throughout the entire API
  • 🤝 Automatically resolve relationships defined in the contract
  • 🎣 Automatic query optimization — the plugin reads the GraphQL selection set and applies .select(...) / .include(...) onto your collection before it runs (solves common N+1 issues)
  • 💅 Types and fields in GraphQL aren't tied to the column names in your database
  • 🔀 Relay integration for nodes and connections that can be efficiently loaded
  • 📚 Supports multiple GraphQL types based on the same contract model (variants)

Example

// Define a GraphQL type backed by a contract model.
builder.prismaObject('User', {
  fields: (t) => ({
    id: t.exposeID('id'),
    email: t.exposeString('email'),
    bio: t.string({
      // Force-load the `profile` relation when this field is queried.
      select: { profile: true },
      resolve: (user) => user.profile?.bio ?? null,
    }),
    // Load `posts` as a list field — nested selections drive .select.
    posts: t.relation('posts', {
      args: { oldestFirst: t.arg.boolean() },
      query: (args) => ({
        orderBy: (p) => (args.oldestFirst ? p.createdAt.asc() : p.createdAt.desc()),
      }),
    }),
    // Relay connection over `posts` with cursor pagination.
    postsConnection: t.relatedConnection('posts', { cursor: 'id' }),
  }),
});

// Create a Relay node backed by a contract model.
builder.prismaNode('Post', {
  id: { field: 'id' },
  collection: (ctx) => ctx.db.orm.Post,
  fields: (t) => ({
    title: t.exposeString('title'),
    author: t.relation('author'),
  }),
});

builder.queryType({
  fields: (t) => ({
    me: t.prismaField({
      type: 'User',
      // Return the orm-client Collection. The plugin auto-applies the
      // selection from `info` and materializes via `.all()`.
      resolve: (_root, _args, ctx) =>
        ctx.db.orm.User.where((u) => u.id.eq(ctx.userId)),
    }),
  }),
});

Given this schema, a query like:

query {
  me {
    email
    posts {
      title
      author {
        id
      }
    }
  }
}

resolves with a single orm-client call. Nested relations stitch into the parent .include(...) chain automatically.

A query with multiple aliases that need different filters:

query {
  me {
    email
    posts {
      title
    }
    oldPosts: posts(oldestFirst: true) {
      title
    }
  }
}

still resolves through one parent call, but the two posts consumers land in one .include('posts', cb => cb.combine({...})) — each alias gets its own combine slot so they don't collide. (orm-client currently falls back to a multi-query plan whenever .combine is used; tracked upstream.)

On this page