Skip to content

Usage of Nexus Graphql

Kharann edited this page Mar 28, 2021 · 5 revisions

In this project, we use nexus for creating our schema with a code-first approach. Code-first approach gives a better developer experience in typescript.

As read in [Project Structure)[https://github.com/Organisasjonskollegiet/voting-backend/wiki/Project-Structure] page, each package is splitted into typedefs, query, mutation and eventual subscriptions.

RootFields vs extendType

When defining one of these Root-types we use the corresponding rootField type, such as queryField, mutationField, subscriptionField.

This project has chosen to use rootTypes as its standard. The advantage of extendType is that we can define multiple operations in one definition(t). However one such definintion can quickly become very big. Therefor its easier to split each operation into each own variable. An advantage with this approach is that each operation is more readable. With rootFields each operation has less tabs, which makes it all more readable. It's also hard to mistake which rootType is in usage.

Inline args vs InputType

Rule of thumb: If the input has more than 3 arguments, create an inputObjectType inside of typedefs.ts. If the Input has less than 3 arguments you can define the argument types inline

Defining graphql fields manually vs using nexus-prisma-plugin

nexus-prisma-plugin provides with a lot of magic and great integration. The plugin will read the prisma-schema and automatically convert them into fields which you can access with t.model. These prisma fields will automatically convert to corresponding graphql-types.

However we should try to mostly define the graphql-fields manually. The plugin is quite mature, however it seems like it hasn't been updated in a while. Therefor we should use the plugin cautiously, even if it can save us a ton of type using it everywhere. In my opinion, we can use t.model when defining custom scalar types, such as DateTime, Json etc.

// Without plugin
export const User = objectType({
    name: 'User',
    definition(t) {
        t.nonNull.id('id');
        t.nonNull.string('username');
        t.nonNull.string('email');
    },
});

// With plugin
export const User = objectType({
    name: 'User',
    definition(t) {
        t.model.();
        t.model.username();
        t.model.email();
    },
});

In addition, we can also use it for simple relations such as One-To-One.

// Without plugin
import { Participant as ParticipantModel } from '@prisma/client'; 
// Suffix the Prisma model type with `Model` so it doesnt clash with variables

export const Participant = objectType({
    name: 'Participant',
    definition(t) {
        t.nonNull.field('role', { type: Role });
        t.nonNull.boolean('isVotingEligible');
        t.field('user', {
            type: User,
            resolve: async (source, _, ctx) => {
                const { userId } = source as ParticipantModel; //
                const user = await ctx.prisma.user.findUnique({ where: { id: userId }, rejectOnNotFound: true });
                return user;
            },
        });
    },
});

// With plugin
export const Participant = objectType({
    name: 'Participant',
    definition(t) {
        t.nonNull.field('role', { type: Role });
        t.nonNull.boolean('isVotingEligible');
        t.model.user();
    },
});