Perceived Problem
There is currently no support for read operations.
Ideas / Proposed Solution(s)
Modify the module generators to generate Query fields and Input types corresponding to the Prisma schema models, as well as the root resolver logic for said query fields.
Overview
I've been working on developing support for read operations in nexus-prisma for use on some projects with my team. It has been very beneficial for us to be able to do this. Ideally, I'd like full crud support but figured the best place to start would be with read since that would probably be the easiest operation to adopt as well as the most helpful one to add. In general, this modification works similarly to the old nexus-plugin-prisma package, but models the newer patterns established by nexus-prisma. I feel pretty good about the stuff we've added for read operations and we are using it on a project with success so far, so I figured it might be a good time to share what I've done with the community! Please note, this patch should not be considered production ready.
I will be creating a PR soon, but figured the easiest way to let the community get their hands on it would be to create a patch file and share that here. This way, if you want to experiment, you can apply the patch directly to your node_modules/nexis-prisma build. This is because that is the simplest, most seamless way to get code generation to operate exactly as before in existing projects. However, please be on the lookup for my PR which I will link in the comment thread below if you would like to reference the source code or play around with that.
Patch
Perform the following steps to install this patch:
Install patch-package
yarn install -D patch-package
Add postinstall script
Add the patch-package command to your postinstall script inside of package.json
{
...
"scripts": {
...
"postinstall": "patch-package",
...
}
...
}
Install nexus-prisma version 2.0.1
You must make sure you have exactly version 2.0.1 installed of nexus-prisma because this is the version that the patch is made off of. You can do this by ensuring you have the following in your package.json:
{
...
"dependencies": {
...
"nexus-prisma": "2.0.1",
...
}
...
}
Also be sure you follow the instruction for in the docs if installing and setting up nexus-prisma in your project for the first time.
Add patch file
Click the link below to download the patch file, and add it to patches/nexus-prisma+2.0.1.patch where patches is a directory in the root of your project. Note that your file path and file name must match exactly in your project.
nexus-prisma+2.0.1.patch
Run yarn install
Run the following script to ensure version 2.0.1 of nexus-prisma gets installed and the patch applied.
Documentation
The subsequent examples are based on the following Prisma schema:
// Prisma Schema
model User {
id Int @id() @default(autoincrement())
email String
name String?
posts Post[]
tenantId Int
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@unique([email, tenantId])
}
model Tenant {
id Int @id() @default(autoincrement())
name String
users User[]
}
model Post {
id Int @id() @default(autoincrement())
title String
userId Int
user User @relation(fields: [userId], references: [id])
}
Query Fields
Generates two query fields for each Prisma model that perform the following Prisma read operations:
findUnique
Each Prisma model gets a corresponding findUnique query field that is named based on a camelCase version of the model's name. For example Query.user
Prior to defining the query field you must also define the operation's [Model]WhereUniqueInput type where [Model] is the name of your model being queried. For example UserWhereUniqueInput.
Basic example
import { inputObjectType, extendType } from 'nexus'
import { Query, UserWhereUniqueInput, User } from 'nexus-prisma'
export const UserObjectType = objectType({
name: User.$name,
definition(t) {
t.field(User.id)
t.field(User.email)
t.field(User.name)
}
})
export const UserWhereUniqueInputObjectType = inputObjectType({
name: UserWhereUniqueInput.$name,
definition(t) {
t.field(UserWhereUniqueInput.id)
t.field(UserWhereUniqueInput.email_tenantId)
}
})
export const UserQueryFields = extendType({
type: 'Query',
definition(t) {
t.field(Query.user)
}
})
query User {
user(where: {
id: 1,
# Or
email_tenantId {
email: "email@email.com",
tenantId: 1,
}
}) {
id
email
name
}
}
Change input field name example
You can also use the as helper method to optionally change the name of your input field if you'd like it to be different than the name Prisma uses under the hood. Note that in the event of combined unique fields, you currently cannot change the name of the nested input types within the combined field.
import { inputObjectType, extendType } from 'nexus'
import { Query, UserWhereUniqueInput } from 'nexus-prisma'
export const UserWhereUniqueInputObjectType = inputObjectType({
name: UserWhereUniqueInput.$name,
definition(t) {
t.field(UserWhereUniqueInput.id)
t.field(UserWhereUniqueInput.email_tenantId.as('email_organizationId'))
}
})
query User {
user(where: {
id: 1,
# Or
email_organizationId {
email: "email@email.com",
tenantId: 1,
}
}) {
id
email
name
}
}
findMany
Each Prisma model also gets a corresponding findMany query field that is named based on a camelCase version of the model's name followed by an s. For example Query.users. Note that currently there is no way to modify the naming convention of findMany query fields and an s is always automatically applied even if it does not make grammatical sense. There are plans to add the ability to change the general naming convention or specific query field names via the Settings in the future.
Prior to defining the query field you must also define the operation's [Model]WhereInput type where [Model] is the name of your model being queried. For example UserWhereInput. Additionally, the where input type contains field configs for AND, OR, and NOT filter types allowing for advanced-nested filtering logic.
Basic example
import { inputObjectType, extendType } from 'nexus'
import { Query, UserWhereInput, User } from 'nexus-prisma'
export const UserObjectType = objectType({
name: User.$name,
definition(t) {
t.field(User.id)
t.field(User.email)
t.field(User.name)
}
})
export const UserWhereInputObjectType = inputObjectType({
name: UserWhereInput.$name,
definition(t) {
t.field(UserWhereInput.AND)
t.field(UserWhereInput.OR)
t.field(UserWhereInput.NOT)
t.field(UserWhereInput.id)
t.field(UserWhereInput.email)
t.field(UserWhereInput.name)
}
})
export const UserQueryFields = extendType({
type: 'Query',
definition(t) {
// ...
t.field(Query.users)
}
})
query Users {
users(where: {
OR: [
{
id: {
equals: 1
}
},
{
id: {
equals: 2
}
}
]
name: {
not: {
equals: "John"
}
}
email: {
contains: "@gmail.com"
}
}) {
list {
id
email
name
}
}
}
Nested filters example
In this example, we must additionally define the TenantWhereInput and PostWhereInput so that we can then add the tenant and posts input fields to our UserWhereInput. This allows us to filter based on relationship sub-fields.
import { inputObjectType, extendType } from 'nexus'
import { Query, TenantWhereInput, PostWhereInput, UserWhereInput } from 'nexus-prisma'
// ...
export const TenantWhereInputObjectType = inputObjectType({
name: TenantWhereInput.$name,
definition(t) {
t.field(TenantWhereInput.AND)
t.field(TenantWhereInput.OR)
t.field(TenantWhereInput.NOT)
t.field(TenantWhereInput.id)
t.field(TenantWhereInput.name)
}
})
export const PostWhereInputObjectType = inputObjectType({
name: PostWhereInput.$name,
definition(t) {
t.field(PostWhereInput.AND)
t.field(PostWhereInput.OR)
t.field(PostWhereInput.NOT)
t.field(PostWhereInput.title)
}
})
export const UserWhereInputObjectType = inputObjectType({
name: UserWhereInput.$name,
definition(t) {
t.field(UserWhereInput.AND)
t.field(UserWhereInput.OR)
t.field(UserWhereInput.NOT)
t.field(UserWhereInput.id)
t.field(UserWhereInput.email)
t.field(UserWhereInput.name)
t.field(UserWhereInput.tenant)
t.field(UserWhereInput.posts)
}
})
// ...
query Users {
users(where: {
OR: [
{
id: {
equals: 1
}
},
{
id: {
equals: 2
}
}
]
name: {
not: {
equals: "John"
}
}
email: {
contains: "@gmail.com"
}
tenant: {
AND: [
{
name: {
contains: "Apple"
}
},
{
name: {
contains: "Inc."
}
}
]
}
posts: {
some: {
title: {
contains: "Hello there"
}
}
}
}) {
list {
id
email
name
}
}
}
Pagination example
You can paginate your queries easily by adding take and/or skip arguments to your query. Your selection can include total and hasMore where total is the total number of results based on your filter (if any) and hasMore is true if there are more records after the amount you took.
query Users {
users(
where: {
name: {
startsWith: "A",
}
}
take: 25
skip: 50
) {
list {
id
email
name
}
total
hasMore
}
}
Field Resolver Filters
With the introduction of where input types for queries as described above, it made sense to also update field resolver logic to be able to handle where filters if provided for list relationship fields. When this is done, the resolver is smart enough to return a paginated result containing list, total, and hasMore as opposed to just a single list. This is not a breaking change since not adding a where filter will still return a single list of results for your existing field resolvers.
Basic example
import { inputObjectType, extendType } from 'nexus'
import { Query, PostWhereInput, User } from 'nexus-prisma'
export const PostWhereInputObjectType = inputObjectType({
name: PostWhereInput.$name,
definition(t) {
t.field(PostWhereInput.AND)
t.field(PostWhereInput.OR)
t.field(PostWhereInput.NOT)
t.field(PostWhereInput.title)
}
})
export const UserObjectType = objectType({
name: User.$name,
definition(t) {
t.field(User.id)
t.field(User.email)
t.field(User.name)
t.field({
...User.posts,
args: {
where: PostWhereInputObjectType
take: 'Int',
skip: 'Int',
}
})
}
})
query Users {
users(
where: {
name: {
startsWith: "A",
}
}
take: 25
skip: 50
) {
list {
id
email
name
posts(
where: {
title: {
contains: "Hello there"
}
},
take: 10
skip: 10
) {
list {
title
}
total
hasMore
}
}
total
hasMore
}
}
Todos
Additional Notes
This issue pertains specifically to read operations and therefore does not include create, update, or delete operations as they relate to the Mutation type. When I have time, I may work on these as well modeling similar patterns set forth herein and then create separate issues / pull requests for those. Ultimately stepping towards the end goal of full crud support. If anyone has any additional ideas or thoughts related to all of the above please let me know below!
Perceived Problem
There is currently no support for read operations.
Ideas / Proposed Solution(s)
Modify the module generators to generate Query fields and Input types corresponding to the Prisma schema models, as well as the root resolver logic for said query fields.
Overview
I've been working on developing support for read operations in
nexus-prismafor use on some projects with my team. It has been very beneficial for us to be able to do this. Ideally, I'd like fullcrudsupport but figured the best place to start would be withreadsince that would probably be the easiest operation to adopt as well as the most helpful one to add. In general, this modification works similarly to the oldnexus-plugin-prismapackage, but models the newer patterns established bynexus-prisma. I feel pretty good about the stuff we've added for read operations and we are using it on a project with success so far, so I figured it might be a good time to share what I've done with the community! Please note, this patch should not be consideredproductionready.I will be creating a PR soon, but figured the easiest way to let the community get their hands on it would be to create a patch file and share that here. This way, if you want to experiment, you can apply the patch directly to your
node_modules/nexis-prismabuild. This is because that is the simplest, most seamless way to get code generation to operate exactly as before in existing projects. However, please be on the lookup for my PR which I will link in the comment thread below if you would like to reference the source code or play around with that.Patch
Perform the following steps to install this patch:
Install
patch-packageAdd
postinstallscriptAdd the
patch-packagecommand to yourpostinstallscript inside ofpackage.json{ ... "scripts": { ... "postinstall": "patch-package", ... } ... }Install
nexus-prismaversion2.0.1You must make sure you have exactly version
2.0.1installed ofnexus-prismabecause this is the version that the patch is made off of. You can do this by ensuring you have the following in yourpackage.json:{ ... "dependencies": { ... "nexus-prisma": "2.0.1", ... } ... }Also be sure you follow the instruction for in the docs if installing and setting up
nexus-prismain your project for the first time.Add patch file
Click the link below to download the patch file, and add it to
patches/nexus-prisma+2.0.1.patchwherepatchesis a directory in the root of your project. Note that your file path and file name must match exactly in your project.nexus-prisma+2.0.1.patch
Run yarn install
Run the following script to ensure version
2.0.1ofnexus-prismagets installed and the patch applied.Documentation
The subsequent examples are based on the following Prisma schema:
Query Fields
Generates two query fields for each Prisma model that perform the following Prisma read operations:
findUnique
Each Prisma model gets a corresponding
findUniquequery field that is named based on acamelCaseversion of the model's name. For exampleQuery.userPrior to defining the query field you must also define the operation's
[Model]WhereUniqueInputtype where[Model]is the name of your model being queried. For exampleUserWhereUniqueInput.Basic example
Change input field name example
You can also use the
ashelper method to optionally change the name of your input field if you'd like it to be different than the name Prisma uses under the hood. Note that in the event of combined unique fields, you currently cannot change the name of the nested input types within the combined field.findMany
Each Prisma model also gets a corresponding
findManyquery field that is named based on acamelCaseversion of the model's name followed by ans. For exampleQuery.users. Note that currently there is no way to modify the naming convention offindManyquery fields and ansis always automatically applied even if it does not make grammatical sense. There are plans to add the ability to change the general naming convention or specific query field names via theSettingsin the future.Prior to defining the query field you must also define the operation's
[Model]WhereInputtype where[Model]is the name of your model being queried. For exampleUserWhereInput. Additionally, the where input type contains field configs forAND,OR, andNOTfilter types allowing for advanced-nested filtering logic.Basic example
Nested filters example
In this example, we must additionally define the
TenantWhereInputandPostWhereInputso that we can then add thetenantandpostsinput fields to ourUserWhereInput. This allows us to filter based on relationship sub-fields.Pagination example
You can paginate your queries easily by adding
takeand/orskiparguments to your query. Your selection can includetotalandhasMorewheretotalis the total number of results based on your filter (if any) andhasMoreis true if there are more records after the amount you took.Field Resolver Filters
With the introduction of where input types for queries as described above, it made sense to also update field resolver logic to be able to handle
wherefilters if provided for list relationship fields. When this is done, the resolver is smart enough to return a paginated result containinglist,total, andhasMoreas opposed to just a single list. This is not a breaking change since not adding awherefilter will still return a single list of results for your existing field resolvers.Basic example
Todos
findManyquery field namesAdditional Notes
This issue pertains specifically to
readoperations and therefore does not includecreate,update, ordeleteoperations as they relate to the Mutation type. When I have time, I may work on these as well modeling similar patterns set forth herein and then create separate issues / pull requests for those. Ultimately stepping towards the end goal of fullcrudsupport. If anyone has any additional ideas or thoughts related to all of the above please let me know below!