Skip to content

Instantly share code, notes, and snippets.

@amit08255
Last active February 3, 2024 01:14
Show Gist options
  • Save amit08255/2401792f840ca67e1260dbc85881f0aa to your computer and use it in GitHub Desktop.
Save amit08255/2401792f840ca67e1260dbc85881f0aa to your computer and use it in GitHub Desktop.
Nexus + Prisma CheatSheet

Nexus + Prisma CheatSheet

Creating model in Prisma

First create generator and datasource entry in your .prisma file like below:

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

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Example 1:

model User {
  id         String    @id @default(cuid())   // ID type column whose default value is cuid
  createdAt  DateTime  @default(now())    // Date type column whose default value is current date and time
  modifiedAt DateTime  @default(now())    // Date type column whose default value is current date and time
  email      String    @unique    // String type column whose value must be unique
  name       String?    // String type column whose value is optional
}

Example 2:

model Question {
  id         String      @id @default(cuid())   // ID type column whose default value is cuid
  createdAt  DateTime    @default(now())    // Date type column whose default value is current date and time
  modifiedAt DateTime    @default(now())    // Date type column whose default value is current date and time
  text       String    // String type column
  options    String[]    // Column value should be a list of string
  answer     String    // String type column
  marks      Int         @default(0)    // Integer type column whose default value is null
  negativeMarking Boolean    @default(false)    // Boolean type value whose default value is false
}

Create relationship between Prisma models

It is very important to remember there is never a prisma model relationship. Relationships are always both sided.

Example 1: Many-to-Many relationship

model User {
  id         String    @id @default(cuid())
  createdAt  DateTime  @default(now())
  modifiedAt DateTime  @default(now())
  email      String    @unique
  projects   Project[]    // Model User has many relationship with Project model
  name       String?
}

model Project {
  id                     String    @id @default(cuid())
  createdAt              DateTime  @default(now())
  modifiedAt             DateTime  @default(now())
  users                  User[]    // Model Project has many relationship with User model
  name                   String
  slug                   String    @unique
  stripeCustomerId       String?   @unique
  stripeSubscriptionId   String?   @unique
  stripePriceId          String?
  stripeCurrentPeriodEnd DateTime?
}

Example 2: Many-to-One relationship

model TestPaper {
  id              String     @id @default(cuid())
  createdAt       DateTime   @default(now())
  modifiedAt      DateTime   @default(now())
  title           String
  description     String
  expireInDays    Int        @default(0)
  maxAttempt      Int        @default(1)
  negativeMarking Boolean    @default(false)
  totalTime       Int        @default(0)
  results         Result[]    // Model TestPaper has many relationship on Result model
}

model Result {
  id         String    @id @default(cuid())
  createdAt  DateTime  @default(now())
  modifiedAt DateTime  @default(now())
  testId     String
  test       TestPaper @relation(fields: [testId], references: [id])    // Model Result has one relationship with TestPaper model
  score      Int
}

Example 3: Mixed relationships

model User {
  id         String    @id @default(cuid())
  createdAt  DateTime  @default(now())
  modifiedAt DateTime  @default(now())
  email      String    @unique
  projects   Project[]
  name       String?
  testsTaken Result[]
}

model Project {
  id                     String    @id @default(cuid())
  createdAt              DateTime  @default(now())
  modifiedAt             DateTime  @default(now())
  users                  User[]
  name                   String
  slug                   String    @unique
  stripeCustomerId       String?   @unique
  stripeSubscriptionId   String?   @unique
  stripePriceId          String?
  stripeCurrentPeriodEnd DateTime?
}

model Question {
  id         String      @id @default(cuid())
  createdAt  DateTime    @default(now())
  modifiedAt DateTime    @default(now())
  text       String
  options    String[]
  answer     String
  marks      Int         @default(0)
  papers     TestPaper[]
}

model TestPaper {
  id              String     @id @default(cuid())
  createdAt       DateTime   @default(now())
  modifiedAt      DateTime   @default(now())
  title           String
  description     String
  expireInDays    Int        @default(0)
  maxAttempt      Int        @default(1)
  questions       Question[]
  negativeMarking Boolean    @default(false)
  totalTime       Int        @default(0)
  results         Result[]
}

model Result {
  id         String    @id @default(cuid())
  createdAt  DateTime  @default(now())
  modifiedAt DateTime  @default(now())
  testId     String
  test       TestPaper @relation(fields: [testId], references: [id])
  userId     String
  user       User      @relation(fields: [userId], references: [id])
  answers    String[]
  score      Int
}

Create model with Nexus

Example 1:

/* eslint-disable no-return-await */
import {
    extendType,
    intArg,
    list,
    nonNull,
    objectType,
    stringArg,
} from 'nexus';
import prisma from '../../db/prisma';

const Question = objectType({
    name: 'Question',
    definition(t) {
        t.nonNull.string('id');    // non-null string column with name `id`
        t.nonNull.string('text');    // non-null string column with name `text`
        t.nonNull.list.nonNull.string('options');    // non-nullable list of string column with name `options`
        t.nonNull.string('answer');    // non-null string column with name `answer`
        t.nonNull.int('marks');    // non-null integer column with name `marks`
        t.nullable.int('expireInDays');    // nullable integer type column
        t.nullable.int('maxAttempt');    // nullable integer type column
        t.nullable.boolean('negativeMarking');    // nullable boolean type column
        t.nullable.int('totalTime');    // nullable integer type column
    },
});

Create model mutation in Nexus for modifying data in model

Example 1:

You must first define the model then create mutation for the model.

const mutations = extendType({
    type: 'Mutation',
    definition: (t) => {
        t.nullable.field('createQuestion', {
            type: 'Question',
            args: {
                text: nonNull(stringArg()),
                answer: nonNull(stringArg()),
                marks: nonNull(intArg()),
                options: nonNull(list(nonNull('String'))),
            },
            
            // Create data in model from args
            resolve: async (_, args, ctx) => {
                return await prisma.question.create({
                    data: {
                        text: args.text,
                        answer: args.answer,
                        marks: args.marks,
                        options: args.options,
                    },
                });
            },
        });

        t.nullable.field('updateQuestion', {
            type: 'Question',
            args: {
                id: nonNull(stringArg()),
                text: nonNull(stringArg()),
                answer: nonNull(stringArg()),
                marks: nonNull(intArg()),
                options: nonNull(list(nonNull('String'))),
            },
            
            // Updates data in question model by ID
            resolve: async (_, args, ctx) => {
                return await prisma.question.update({
                    where: { id: args.id },
                    data: {
                        text: args.text,
                        answer: args.answer,
                        marks: args.marks,
                        options: args.options,
                    },
                });
            },
        });
    },
});

Graphql mutation query for above mutation will look like below:

mutation CreateQuestion(
  $text: String!
  $answer: String!
  $marks: Int!
  $options: [String!]!
  $topicId: ID
) {
  createQuestion(
    text: $text
    answer: $answer
    marks: $marks
    options: $options
    topicId: $topicId
  ) {
    id
  }
}
mutation UpdateQuestion(
  $id: String!
  $text: String!
  $answer: String!
  $marks: Int!
  $options: [String!]!
) {
  updateQuestion(
    id: $id
    text: $text
    answer: $answer
    marks: $marks
    options: $options
  ) {
    id
  }
}

Create Nexus model query to get data from model

Example 1: Get data by id in argument

import {
    arg,
    booleanArg,
    extendType,
    intArg,
    list,
    nonNull,
    objectType,
    stringArg,
} from 'nexus';

const queries = extendType({
    type: 'Query',
    definition: (t) => {
        t.field('question', {
            type: 'Question',
            args: {
                id: nonNull(stringArg()),
            },
            // Query data from question model by ID from args
            resolve: (_, { id }: any, ctx) => {
                return prisma.question.findUnique({
                    where: {
                        id,
                    },
                });
            },
        });
    },
});

The GraphQuery query object for above will look like below:

query GetQuestion($id: String!) {
  question(id: $id) {
    id
    text
    options
    answer
    marks
  }
}

Here the question in query is same as name of field in our query definition.

Example 2: Get all data using query

const queries = extendType({
    type: 'Query',
    definition: (t) => {
        // get all papers
        t.list.field('papers', {
            type: 'TestPaper',
            resolve: (_root, _args, ctx) => {
                if (!ctx.user?.id) return null;
                return prisma.testPaper.findMany();
            },
        });
    },
});

The GraphQuery query object for above will look like below:

query GetPaperList {
  papers {
    id
    title
    description
    expireInDays
    maxAttempt
    negativeMarking
    totalTime
  }
}

Here the papers in query is same as name of field in our query definition.

Creating Nexus model column to connect with ID of other model

Example 1:

import {
    arg,
    booleanArg,
    extendType,
    intArg,
    list,
    nonNull,
    objectType,
    stringArg,
} from 'nexus';
import prisma from '../../db/prisma';

const TestPaper = objectType({
    name: 'TestPaper',
    definition(t) {
        t.nonNull.string('id');
        t.nonNull.string('title');
        t.nonNull.string('description');
        t.nullable.int('expireInDays');
        t.nullable.int('maxAttempt');
        t.nullable.boolean('negativeMarking');
        t.nullable.int('totalTime');

        // questions field will be of type question ID
        // which will then be resolved from question table records using ID. It will be a list of ID
        t.nonNull.list.nonNull.field('questions', {
            type: 'Question',
            resolve: (parent, _, ctx) => ctx.prisma.testPaper
                .findUnique({ where: { id: parent.id } })
                .questions(),
        });
    },
});

The prisma model for above model will look like below:

model TestPaper {
  id              String     @id @default(cuid())
  createdAt       DateTime   @default(now())
  modifiedAt      DateTime   @default(now())
  title           String
  description     String
  expireInDays    Int        @default(0)
  maxAttempt      Int        @default(1)
  questions       Question[]
  negativeMarking Boolean    @default(false)
  totalTime       Int        @default(0)
}

Example 2:

const Question = objectType({
    name: 'Question',
    definition(t) {
        t.nonNull.string('id');
        t.nonNull.string('text');
        t.nonNull.list.nonNull.string('options');
        t.nonNull.string('answer');
        t.nonNull.int('marks');

        // topicId field will be of type topic ID
        // which will then be resolved from topic table records
        t.nonNull.field('topic', {
            type: 'Topic',
            // eslint-disable-next-line max-len
            resolve: (parent: any, _: any, ctx: any) => ctx.prisma.question.findUnique({ where: { id: parent.id } }).topic(),
        });
    },
});

The prisma model for above model will look like below:

model Question {
  id         String      @id @default(cuid())
  createdAt  DateTime    @default(now())
  modifiedAt DateTime    @default(now())
  text       String
  options    String[]
  answer     String
  marks      Int         @default(0)
  papers     TestPaper[]
  topicId    String
  topic      Topic       @relation(fields: [topicId], references: [id])
}

Nexus mutation of above model will look like below:

const mutations = extendType({
    type: 'Mutation',
    definition: (t) => {
        t.nullable.field('createQuestion', {
            type: 'Question',
            args: {
                text: nonNull(stringArg()),
                answer: nonNull(stringArg()),
                marks: nonNull(intArg()),
                options: nonNull(list(nonNull('String'))),
                
                // topic ID of type ID which links to topic in model
                topicId: arg({
                    type: 'ID',
                }),
            },
            resolve: async (_, args: any, ctx) => {
                if (!ctx.user?.id) return null;

                return await prisma.question.create({
                    data: {
                        text: args.text,
                        answer: args.answer,
                        marks: args.marks,
                        options: args.options,
                        topic: {
                            connect: { id: args.topicId } || null,
                        },
                    },
                });
            },
        });

        t.nullable.field('updateQuestion', {
            type: 'Question',
            args: {
                id: nonNull(stringArg()),
                text: nonNull(stringArg()),
                answer: nonNull(stringArg()),
                marks: nonNull(intArg()),
                options: nonNull(list(nonNull('String'))),
            },
            resolve: async (_, args, ctx) => {
                if (!ctx.user?.id) return null;

                return await prisma.question.update({
                    where: { id: args.id },
                    data: {
                        text: args.text,
                        answer: args.answer,
                        marks: args.marks,
                        options: args.options,
                    },
                });
            },
        });
    },
});

Creating Nexus mutation with connection to other model ID

Example 1:

const mutations = extendType({
    type: 'Mutation',
    definition: (t) => {
        t.nullable.field('createTestPaper', {
            type: 'TestPaper',
            args: {
                title: nonNull(stringArg()),
                description: nonNull(stringArg()),
                expireInDays: intArg(),
                maxAttempt: intArg(),
                negativeMarking: booleanArg(),
                totalTime: intArg(),
                // Questions will contain any array of ID
                questions: arg({
                    type: list('ID'),
                }),
            },
            resolve: async (_, args: any, ctx) => {
                if (!ctx.user?.id) return null;

                return await prisma.testPaper.create({
                    data: {
                        title: args.title,
                        description: args.description,
                        expireInDays: args.expireInDays || 0,
                        maxAttempt: args.maxAttempt || 1,
                        negativeMarking: args.negativeMarking || false,
                        totalTime: args.totalTime || 0, // 0 = infinite time
                        // connecting question ID list in args with question table records
                        questions: {
                            connect: args.questions.map((que) => ({ id: que })) || [],
                        },
                    },
                });
            },
        });
    },
});

Graphql mutation query for above mutation will look like below:

mutation CreateTestPaper(
  $title: String!
  $description: String!
  $expireInDays: Int
  $maxAttempt: Int
  $negativeMarking: Boolean
  $totalTime: Int
  $questions: [ID!]!
) {
  createTestPaper(
    title: $title
    description: $description
    expireInDays: $expireInDays
    maxAttempt: $maxAttempt
    negativeMarking: $negativeMarking
    totalTime: $totalTime
    questions: $questions
  ) {
    id
  }
}

Creating Nexus query for many-to-many relationship

Example 1: Get ID of data from other row in many-to-many relationship

Nexus object model for the data model:

const TestPaper = objectType({
    name: 'TestPaper',
    definition(t) {
        t.nonNull.string('id');
        t.nonNull.string('title');
        t.nonNull.string('description');
        t.nullable.int('expireInDays');
        t.nullable.int('maxAttempt');
        t.nullable.boolean('negativeMarking');
        t.nullable.int('totalTime');

        // questions field will be of type question ID
        // which will then be resolved from question table records
        t.nonNull.list.nonNull.field('questions', {
            type: 'Question',
            resolve: (parent, _, ctx) => ctx.prisma.testPaper
                .findUnique({ where: { id: parent.id } })
                .questions(),
        });
    },
});
const queries = extendType({
    type: 'Query',
    definition: (t) => {
    // get all papers
        t.list.field('papers', {
            type: 'TestPaper',
            resolve: (_root, _args, ctx) => {
                if (!ctx.user?.id) return null;
                
                // Include questions in query response
                return prisma.testPaper.findMany({
                    include: { questions: true },
                });
            },
        });
        
        // Get paper by ID with question
        t.field('paperWithQuestion', {
            type: 'TestPaper',
            args: {
                id: nonNull(stringArg()),
            },
            resolve: (_, { id }: any, ctx) => {
                if (!ctx.user?.id) return null;
                
                // Include questions in query response
                return prisma.testPaper.findUnique({
                    where: {
                        id,
                    },
                    include: { questions: true },
                });
            },
        });
    },
});

Graphql query for above query object will look like below:

Return only question ID list with query response along with test paper details:

query GetPaperList {
  papers {
    id
    title
    description
    expireInDays
    maxAttempt
    negativeMarking
    totalTime
    questions {
      id
    }
  }
}

Return question info list with query response along with test paper details:

query GetPaperWithQuestion($id: String!) {
  paperWithQuestion(id: $id) {
    id
    title
    description
    expireInDays
    maxAttempt
    negativeMarking
    totalTime
    questions {
      id
      text
      options
      answer
      marks
    }
  }
}

Soft Delete

Update model with deleted field like below:

model User {
  id         String    @id @default(cuid())
  createdAt  DateTime  @default(now())
  modifiedAt DateTime  @default(now())
  email      String    @unique
  name       String?
  deleted Boolean @default(false)
}

Update nexus model object like below:

const User = objectType({
    name: 'User',
    definition(t) {
        t.nonNull.string('id');
        t.nullable.string('name');
        t.nonNull.string('email');
        t.nullable.boolean('deleted');
    },
});

Update mutation of model like below:

t.field('deleteUserById', {
    type: 'User',
    args: {
        id: nonNull(stringArg()),
    },
    resolve: async (_root, { id }, ctx) => {
        if (!ctx.user?.id) return null;

        return await prisma.user.delete({
            where: { id },
        });
    },
});

Add prisma middleware for soft deletion and not returning deleted data:

/* eslint-disable vars-on-top,import/no-mutable-exports */
import { PrismaClient } from '@prisma/client';

// Make global.cachedPrisma work with TypeScript
declare global {
  // NOTE: This actually needs to be a "var", let/const don't work here.
  // eslint-disable-next-line no-var
  var cachedPrisma: PrismaClient;
}

// Workaround to make Prisma Client work well during "next dev"
// @see https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices
let prisma: PrismaClient;
if (process.env.NODE_ENV === 'production') {
    prisma = new PrismaClient();
} else {
    if (!global.cachedPrisma) {
        global.cachedPrisma = new PrismaClient();
    }
    prisma = global.cachedPrisma;
}

/** ******************************** */
/* SOFT DELETE MIDDLEWARE */
/** ******************************** */
prisma.$use(async (paramData, next) => {
    const params = { ...paramData };
    // Check incoming query type
    if (params.action === 'delete') {
        // Delete queries
        // Change action to an update
        params.action = 'update';
        params.args.data = { deleted: true };
    }
    if (params.action === 'deleteMany') {
        // Delete many queries
        params.action = 'updateMany';
        if (params.args.data !== undefined) {
            params.args.data.deleted = true;
        } else {
            params.args.data = { deleted: true };
        }
    }
    if (paramData.action === 'findFirst' || paramData.action === 'findMany' || paramData.action === 'findUnique') {
        const conditions = paramData.args.where || {};
        params.args.where = { ...conditions, deleted: false };
    }

    return next(params);
});

export default prisma;

Bulk Updates

Add inputObjectType for defining array of JSON inputs.

const Sprints = inputObjectType({
    name: 'Sprints',

    definition(t) {
        t.nullable.string('id');
        t.nonNull.string('taskId');
        t.nonNull.int('timeSpent');
        t.nullable.boolean('isCompleted');
    },

});

Use custom input type in mutation args.

t.nullable.list.field('createDailySprints', {
    type: 'DailySprint',
    args: {
        sprints: nonNull(list(Sprints)),
    },
    resolve: async (_, args, ctx) => {
        if (!ctx.user?.id) return null;

        const results = [];

        args.sprints.map((sprint) => {
            const query = {
                timeSpent: sprint.timeSpent,
                isCompleted: sprint.isCompleted,
            };

            if (sprint.id) {
                const dailySprint = prisma.dailySprint.findFirst({
                    where: {
                        id: sprint.id,
                    },
                });

                if (dailySprint && dailySprint.userId !== ctx.user.id) {
                    return;
                }

                if (dailySprint) {
                    results.push(prisma.dailySprint.update({
                        where: { id: sprint.id },
                        data: query,
                    }));
                }
            }

            results.push(prisma.dailySprint.create({
                data: {
                    ...query,
                    id: sprint.id || cuid(),
                    user: {
                        connect: { id: ctx.user?.id },
                    },
                    task: {
                        connect: { id: sprint.taskId },
                    },
                },
            }));
        });

        return await Promise.all(results);
    },
});

Use input type in GraphQL mutation.

mutation CreateDailySprints(
  $sprints: [Sprints!]!
) {
  createDailySprints(sprints: $sprints) {
    id
    timeSpent
    isCompleted
    task {
      id
      date
      subject
      status
      category
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment