0.0.0-dripip • Published 3 years ago

medz.try v0.0.0-dripip

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

⚠️ Currently in early preview - not to be used in Production unless you're willing to live on the bleeding edge and give us feedback, which we would welcome!. Follow progress here.

nexus-prisma

trunk

Official Prisma plugin for Nexus.

Usage

  1. Install dependencies

    npm add nexus-prisma nexus graphql @prisma/client
    npm add --dev prisma

    nexus graphql and @prisma/client are peer dependencies. prisma is for the Prisma CLI which you'll probably want during development.

    If you use nexus@=<1.0 then you must use t.field(<NAME>, <CONFIG>) instead of t.field(<CONFIG>). The Nexus Prisma docs assume the latter form.

  2. Add a nexus-prisma generator block to your Prisma Schema.

    If you are using prisma@=<2.17 then you must use the Nexus Prisma Prisma generator name of nexus_prisma instead of nexus-prisma. See notes for more detail.

  3. Run prisma generate in your terminal.

  4. Import models from nexus-prisma and then pass them to your Nexus type definition and field definition configurations. In this way you will be effectively projecting models from your data layer into GraphQL types in your API layer.

Example
generator client {
  provider = "prisma-client-js"
}

generator nexusPrisma {
   provider = "nexus-prisma"
// provider = "nexus_prisma"    <-- For prisma@=<2.17 users
}

/// This is a user!
model User {
  /// This is an id!
  id  String  @id
}
prisma generate
import { User } from 'nexus-prisma'
import { makeSchema, objectType } from 'nexus'

export const schema = makeSchema({
  types: [
    objectType({
      name: User.$name
      description: User.$description
      definition(t) {
        t.field(User.id)
     // t.field(User.id.name, User.id)    <-- For nexus@=<1.0 users
      }
    })
  ]
})

Roadmap

Done
  • (#4) Support for Prisma Model field types that map to standard GraphQL scalars
  • (#8) Support for Prisma Model field types of DateTime & Json
  • (#16) Support for Prisma enums
  • (#25, #36) Basic support for Prisma Model field types relating to other Models 1:1
  • (#38) Basic support for Prisma Model field types relating to other Models 1:n
  • (#43) Support for runtime and gentime settings
  • (#61) JSDoc for settings/$settings
  • (#68) Support for Prisma Model field type Bytes
Shortterm
  • (#59) Support for Prisma Model field type BigInt
  • (#94) Support for Prisma Model field type Decimal
  • Improved JSDoc for relation 1:1 & 1:n fields
Midterm
  • Support for Prisma Model field types relating to other Models n:n
  • Support for relation field ordering parameters
  • (#83) Support for relation field filtering parameters
  • Support for relation field pagination parameters
Longterm
  • Nexus Plugin? t.model? t.crud?
  • ...

Architecture

nexus-prisma-architecture

  1. You or a script (CI, programmatic, etc.) run $ prisma generate.
  2. Prisma generator system reads your Prisma schema file
  3. Prisma generator system runs the Nexus Prisma generator passing it the "DMMF", a structured representation of your Prisma schema.
  4. Nexus Prisma generator reads your Nexus Prisma generator configuration if present.
  5. Nexus Prisma generator writes generated source code into its own package space in your node_modules.
  6. Later when you run your code it imports nexus-prisma which hits the generated entrypoint.
  7. The generated runtime is actually thin, making use of a larger static runtime.

Features

Note: ⛑ The following use abbreviated examples that skip a complete setup of passing Nexus type definition to Nexus' makeSchema. If you are new to Nexus, consider reading the official Nexus tutorial before jumping into Nexus Prisma.

Type-safe Generated Library Code

Following the same philosophy as Prisma Client, Nexus Prisma uses generation to create an API that feels tailor made for your project.

model User {
  id  String  @id
}
import { User } from 'nexus-prisma'
import { objectType } from 'nexus'

objectType({
  name: User.$name
  description: User.$description
  definition(t) {
    t.field({
      type: User.id.type,
      description: User.id.description
    })
  }
})

Project Enums

Every enum defined in your Prisma schema becomes importable as a Nexus enum type definition configuration. This makes it trivial to project enums from your database layer into your API layer.

enum SomeEnum {
  foo
  bar
}
import { SomeEnum } from 'nexus-prisma'
import { enumType } from 'nexus'

SomeEnum.name //    'SomeEnum'
SomeEnum.members // ['foo', 'bar']

enumType(SomeEnum)

Project Scalars

Like GraphQL, Prisma has the concept of scalar types. Some of the Prisma scalars can be naturally mapped to standard GraphQL scalars. The mapping is as follows:

Prisma Standard Scalar to GraphQL Standard Scalar Mapping

PrismaGraphQL
BooleanBoolean
StringString
IntInt
FloatFloat
String with @idID
Int with @idID | Int (configurable)

However some of the Prisma scalars do not have a natural standard representation in GraphQL. For these cases Nexus Prisma generates code that references type names matching those scalar names in Prisma. Then, you are expected to define those custom scalar types in your GraphQL API. Nexus Prisma ships with pre-defined mappings in nexus-prisma/scalars you can use for convenience. The mapping is as follows:

Prisma Standard-Scalar to GraphQL Custom-Scalar Mapping

PrismaGraphQLNexus t HelperGraphQL Scalar ImplementationAdditional Info
JsonJsonjsonJsonObject
DateTimeDateTimedateTimeDateTime
BigIntBigIntbigIntBigIntJavaScript BigInt
BytesBytesbytesByteNode.js Buffer
DecimalDecimaldecimal(internal)Uses Decimal.js

Note: Not all Prisma scalar mappings are implemented yet: Unsupported

Note: BigInt is supported in Node.js since version 10.4.0 however to support BigInt in JSON.parse/JSON.stringify you must use json-bigint-patch otherwise BigInt values will be serialized as strings.

You can use your own GraphQL Scalar Implementation, however, you must adhear to the above Prisma/GraphQL name mapping defined above.

Here is an example using Nexus Prisma's pre-defined GraphQL custom scalars:

import NexusPrismaScalars from 'nexus-prisma/scalars'
import { makeSchema } from 'nexus'

makeSchema({
  types: [NexusPrismaScalars],
})

There is a recipe below showing how to add your own custom scalars if you want.

Project Relations

You can project relations into your API with Nexus Prisma. Nexus Prisma even includes the resolver you'll need at runtime to fulfill the projection by automating use of your Prisma Client instance.

Please note that not all kinds of relationships are supported yet. Details about projecting each kind of relation are documented in their respective sections. This section only contains general documentation common to all.

To project relations you must by default expose an instance of Prisma Client on the GraphQL context under the key name prisma. You can customize which context property Nexus Prisma should look for your Prisma Client.

Example: Exposing Prisma Client on GraphQL Context with Apollo Server

import { ApolloServer } from 'apollo-server'
import { PrismaClient } from '@prisma/client'
import schema from './your/schema/somewhere'

const prisma = new PrismaClient()

new ApolloServer({
  schema,
  context() {
    return {
      prisma,
    }
  },
})

Project 1:1 Relation

You can project 1:1 relationships into your API.

Example: Tests

The integration test suite is a useful reference as it is declarative (easy to read) and gives a known-working example spanning from database all the way to executed GraphQL document.

Example: Full 1:1

// Database Schema

model User {
  id         String  @id
  profile    Profile @relation(fields: [profileId], references: [id])
  profileId  String
}

model Profile {
  id      String  @id
  user    User?
}
// API Schema

import { User, Profile } from 'nexus-prisma'

queryType({
  definition(t) {
    t.nonNull.list.nonNull.field('users', {
      type: 'User',
      resolve(_, __, ctx) {
        return ctx.prisma.user.findMany()
      },
    })
  },
})

objectType({
  name: User.$name,
  definition(t) {
    t.field(User.id)
    t.field(User.profile)
  },
})

objectType({
  name: Profile.$name,
  definition(t) {
    t.field(Profile.id)
  },
})
# API Schema Represented in GraphQL SDL (this is generated by Nexus)

type Query {
  users: [User!]!
}

type User {
  id: ID
  profile: Profile
}

type Profile {
  id: ID
}
// Example Database Data (for following example)

await prisma.user.create({
  data: {
    id: 'user1',
    profile: {
      create: {
        id: 'profile1',
      },
    },
  },
})
# Example API Client Query

query {
  users {
    id
    profile {
      id
    }
  }
}
{
  "data": {
    "users": [
      {
        "id": "user1",
        "profile": {
          "id": "profile1"
        }
      }
    ]
  }
}

Limitation: Nullable on Without-Relation-Scalar Side

Prisma requires that a 1:1 relationship has one side that is optional. For example in the following it is not possible for Profile to have a required relationship to User. For more detail you can read the Prisma docs about this here.

model User {
  id         String  @id
  profile    Profile @relation(fields: [profileId], references: [id])
  profileId  String
}

model Profile {
  id      String  @id
  user    User?  // <--  "?" required
}

Prisma inherits this limitation from databases. In turn Nexus Prisma inherits this limitation from Prisma. For example consider this projection and then look at the resulting GraphQL SDL representation.

import { User, Profile } from 'nexus-prisma'

objectType({
  name: User.$name,
  definition(t) {
    t.field(User.id)
    t.field(User.profile)
  },
})

objectType({
  name: Profile.$name,
  definition(t) {
    t.field(Profile.id)
    t.field(User.profile)
  },
})
type User {
  id: ID
  profile: Profile!
}

type Profile {
  id: ID
  user: User # <-- Nullable!
}

This limitation may be a problem for your API. There is an issue track this that you can subscribe to if interested. As a workaround for now you can do this:

objectType({
  name: Profile.$name,
  definition(t) {
    t.field(Profile.id)
    t.field({
      ...User.profile,
      type: nonNull(User.profile.type),
    })
  },
})

Project 1:n Relation

You can project 1:n relationships into your API.

Example: Tests

The integration test suite is a useful reference as it is declarative (easy to read) and gives a known-working example spanning from database all the way to executed GraphQL document.

Example: Full 1:n

// Database Schema

model User {
  id         String    @id
  posts      Post[]
}

model Post {
  id        String  @id
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  String
}
// API Schema

import { User, Post } from 'nexus-prisma'

queryType({
  definition(t) {
    t.nonNull.list.nonNull.field('users', {
      type: 'User',
      resolve(_, __, ctx) {
        return ctx.prisma.user.findMany()
      },
    })
  },
})

objectType({
  name: User.$name,
  definition(t) {
    t.field(User.id)
    t.field(User.posts)
  },
})

objectType({
  name: Post.$name,
  definition(t) {
    t.field(Post.id)
  },
})
# API Schema Represented in GraphQL SDL (this is generated by Nexus)

type Query {
  users: [User]
}

type User {
  id: ID!
  posts: [Post!]!
}

type Post {
  id: ID!
}
// Example Database Data (for following example)

await prisma.user.create({
  data: {
    id: 'user1',
    posts: {
      create: [{ id: 'post1' }, { id: 'post2' }],
    },
  },
})
# Example API Client Query

query {
  users {
    id
    posts {
      id
    }
  }
}
{
  "data": {
    "users": [
      {
        "id": "user1",
        "posts": [
          {
            "id": "post1"
          },
          {
            "id": "post2"
          }
        ]
      }
    ]
  }
}

Runtime Settings

Reference

prismaClientContextField: string
  • @summary The name of the GraphQL context field to get an instance of Prisma Client from.
  • @remarks The instance of Prisma Client found here is accessed in the default resolvers for relational fields.
  • @default "prisma"
  • @example

    // src/main.ts
    
    import { PrismaClient } from '@prisma/client'
    import { ApolloServer } from 'apollo-server'
    import { makeSchema } from 'nexus'
    import { User, Post, $settings } from 'nexus-prisma'
    
    new ApolloServer({
      schema: makeSchema({
        types: [],
      }),
      context() {
        return {
          db: new PrismaClient(), // <-- You put Prisma client on the "db" context property
        }
      },
    })
    
    $settings({
      prismaClientContextField: 'db', // <-- Tell Nexus Prisma
    })
prismaClientImportId: string
  • @summary Where Nexus Prisma will try to import your generated Prisma Client from. You should not need to configure this normally because Nexus Prisma generator automatically reads the Prisma Client generator output setting if you have set it. The value here will be used in a dynamic import thus following Node's path resolution rules. You can pass a node_modules package like foo @prisma/client, @my/custom/thing etc. or you can pass an absolute module/file path /my/custom/thing / /my/custom/thing/index.js or finally a relative path to be resolved relative to the location of Nexus Prisma source files (you probably don't want this).

  • @default @prisma/client

  • @remarks Nexus Prisma imports Prisma client internally for two reasons: 1) validation wherein a class reference to Prisma Client is needed for some instanceof checks and 2) for acquiring the DMMF as Nexus Prisma relies on some post-processing done by Prisma Client generator.

  • @example

    // src/main.ts
    
    import { PrismaClient } from '@prisma/client'
    import { ApolloServer } from 'apollo-server'
    import { makeSchema } from 'nexus'
    import { User, Post, $settings } from 'nexus-prisma'
    
    new ApolloServer({
      schema: makeSchema({
        types: [],
      }),
    })
    
    $settings({
      prismaClientImportId: '@my/custom/thing',
    })

Generator Settings

You are able to control certain aspects of the Nexus Prisma code generation.

Usage

  1. Create a configuration file named any of:

    nexusPrisma.ts  /  nexus-prisma.ts  /  nexus_prisma.ts

    In one of the following directories:

    1. Project Root – The directory containing your project's package.json. Example:

        ├── nexus-prisma.ts
        └── package.json
    2. Primsa Directory – The directory containing your Prisma schema. Example:

        ├── prisma/nexus-prisma.ts
        └── package.json
  2. Import the settings singleton and make your desired changes. Example:

    import { settings } from 'nexus-prisma/generator'
    
    settings({
      projectIdIntToGraphQL: 'ID',
    })

Reference

projectIdIntToGraphQL: 'ID' | 'Int'
  • @summary Map Prisma model fields of type Int with attribute @id to ID or Int.
  • @default Int
docPropagation.JSDoc: boolean
  • @summary Should Prisma Schema docs propagate as JSDoc?
  • @default true
docPropagation.GraphQLDocs: boolean
  • @summary Should Prisma Schema docs propagate as GraphQL docs?
  • @remarks When this is disabled it will force .description property to be undefined. This is for convenience, allowing you to avoid post-generation data manipulation or consumption contortions.
  • @default true

Prisma String @id fields project as GraphQL ID fields

All String fields with @id attribute in your Prisma Schema get projected as GraphQL ID types rather than String types.

model User {
  id  String  @id
}
import { User } from 'nexus-prisma'
import { objectType } from 'nexus'

objectType({
  name: User.$name
  description: User.$description
  definition(t) {
    t.field(User.id)
  }
})
type User {
  id: ID
}

Prisma Schema Docs Propagation

As GraphQL schema doc

/// A user.
model User {
  /// A stable identifier to find users by.
  id  String  @id
}
import { User } from 'nexus-prisma'
import { objectType } from 'nexus'

User.$description // JSDoc: A user.
User.id.description // JSDoc: A stable identifier to find users by.

objectType({
  name: User.$name
  description: User.$description
  definition(t) {
    t.field(User.id)
  }
})
"""
A user.
"""
type User {
  """
  A stable identifier to find users by.
  """
  id: ID
}

As JSDoc

Can be disabled in gentime settings.

/// A user.
model User {
  /// A stable identifier to find users by.
  id  String  @id
}
import { User } from 'nexus-prisma'

User // JSDoc: A user.
User.id // JSDoc: A stable identifier to find users by.

Refined DX

These are finer points that aren't perhaps worth a top-level point but none the less add up toward a thoughtful developer experience.

JSDoc
  • Generated Nexus configuration for fields and models that you have not documented in your PSL get default JSDoc that teaches you how to do so.
  • JSDoc for Enums have their members embedded
Default Runtime

When your project is in a state where the generated Nexus Prisma part is missing (new repo clone, reinstalled deps, etc.) Nexus Prisma gives you a default runtime export named PleaseRunPrismaGenerate and will error with a clear message.

Peer-Dependency Validation

When nexus-prisma is imported it will validate that your project has peer dependencies setup correctly.

If a peer dependency is not installed it nexus-prisma will log an error and then exit 1. If its version does not satify the range supported by the current version of nexus-prisma that you have installed, then a warning will be logged. If you want to opt-out of this validation then set an envar as follows:

NO_PEER_DEPENDENCY_CHECK=true|1
PEER_DEPENDENCY_CHECK=false|0
Auto-Import Optimized
  • nexus-prisma/scalars offers a default export you can easily auto-import by name: NexusPrismaScalars.

Recipes

Project relation with custom resolver logic

Nexus Prisma generates default GraphQL resolvers for your model relation fields. However you may want to run custom logic in the resolver. This is easy to do. The following show a few ways.

  1. Wrap Style You can access the default resolver within your own custom resolver.

    objectType({
      name: User.$name,
      definition(t) {
        t.field(User.id)
        t.field({
          ...User.posts,
          async resolve(...args) {
            // Your custom before-logic here
            const result = await User.posts.resolve(...args)
            // Your custom after-logic here
            return result
          },
        })
      },
    })
  2. Replace Style You can simply opt out of using the default resolver completely:

    objectType({
      name: User.$name,
      definition(t) {
        t.field(User.id)
        t.field({
          ...User.posts,
          async resolve(...args) {
            // Your custom logic here
          },
        })
      },
    })

Supply custom custom scalars to your GraphQL schema

The following is a brief example how you could add your own custom GraphQL custom scalars to satisfy Nexus Prisma. Note that most of the time using the defaults exported by nexus-prisma/scalars will probably be good enough for you.

import { GraphQLScalarType } from 'graphql'
import { JSONObjectResolver, DateTimeResolver } from 'graphql-scalars'
import { asNexusMethod, makeSchema } from 'nexus'

const jsonScalar = new GraphQLScalarType({
  ...JSONObjectResolver,
  // Override the default 'JsonObject' name with one that matches what Nexus Prisma expects.
  name: 'Json',
})

const dateTimeScalar = new GraphQLScalarType(DateTimeResolver)

makeSchema({
  types: [asNexusMethod(jsonScalar, 'json'), asNexusMethod(dateTimeScalar, 'dateTime')],
})

Notes

For users of nexus-prisma@=<0.20

Versions of nexus-prisma package prior to 0.20 were a completely different version of the API, and had also become deprecated at one point to migrate to nexus-plugin-prisma when Nexus Framework was being worked on. All of that is history.

For users of prisma@=<2.17

If you are using prisma@=<2.17 then you must use the Nexus Prisma Prisma generator name of nexus_prisma instead of nexus-prisma. This is because prior to prisma@2.18 there was a hardcode check for nexus-prisma that would fail with an error message about a now-old migration.

For users of nexus@=<1.0

The release of Nexus 1.1 introduced an overload to t.field allowing improved usage of Nexus Prisma. The difference is as follows. Note if you prefer the older way that is and always will be supported too.

import { User } from 'nexus-prisma'
import { makeSchema, objectType } from 'nexus'

export const schema = makeSchema({
  types: [
    objectType({
      name: User.$name
      description: User.$description
      definition(t) {
+        t.field(User.id) //                 <-- for nexus@>=1.1 users
-        t.field(User.id.name, User.id) //   <-- For nexus@=<1.0 users
      }
    })
  ]
})

Supported Versions Of Node & Prisma

We only officially support what we test.

We test Node versions that are Active LTS and Current. For which versions of Node that equals you can refer to our tests or look here: https://nodejs.org/en/about/releases.

We test Prisma versions 2.27. More Prisma versions are planned to be tested, refer to #69.

We do not currently maintain a historical matrix of what past versions of Prisma supported what vesrions of Prisma and Node.