Code-first schema definition
Tags: graphql, nexus, nuxt3
Published: 2022-02-23 13:40:33

Having the Prisma client ready to work with our data on the server side is great, although we still need to define and expose a GraphQL API for interacting with the front end.

But before setting up the GraphQL API endpoint, it's a good idea to create a basic schema first.

## Defining the schema using Nexus types

The code-first approach we propose here is based on [Nexus](https://nexusjs.org/), since it will eventually have first-class integration with Prisma (see [here](https://github.com/prisma/nexus-prisma)).

Let's add Nexus and GraphQL to our project:
```shell
yarn add -D nexus graphql
```

We can structure the server-side source code to make it somewhat scaleable by having our main schema file in `server/schema.ts` and all of our Nexus types in `nexus/*.ts` (we don't use `types/` to avoid confusion with TypeScript types).

Also, exporting all of these types from a single source file simplifies the schema construction, and naming it `server/nexus/_types.ts` will keep this file above the others inside the directory. We could also use `server/nexus/**/*.ts` and `server/nexus/index.ts` for larger projects.

With this structure in mind, let's create `server/schema.ts`:
```typescript
import { resolve } from "pathe";
import { GraphQLSchema } from "graphql";
import { makeSchema } from "nexus";
import * as types from "./nexus/_types";

export default makeSchema({
  types,
  shouldGenerateArtifacts: process.env.NODE_ENV === "development",
  outputs: {
    schema: resolve(process.cwd(), "generated/schema.graphql"),
    typegen: resolve(process.cwd(), "generated/nexus-types.ts"),
  },
}) as unknown as GraphQLSchema;
```

_We need to cast the schema using `as unknown as GraphQLSchema` until Nexus properly supports GraphQL 16, see this [issue](https://github.com/graphql-nexus/nexus/issues/1019) for details._

We set `shouldGenerateArtifacts` and `outputs` so that Nexus will generate the exposed GraphQL schema and its corresponding typings in the `generated/` directory (in development mode only). These are useful for type safety and generating further artifacts with `graphql-codegen` (see the next article of this series).

Since we only want a minimal schema at this point, let's create a simple `hello` query in `server/nexus/hello.ts` (see the [Nexus documentation](https://nexusjs.org/docs/) to learn more):
```typescript
import { extendType } from "nexus";

export const HelloQuery = extendType({
  type: "Query",
  definition(t) {
    t.nonNull.field("hello", {
      type: "String",
      resolve: () => `Hello Nexus`,
    });
  },
});
```

... and export it in `server/nexus/_types.ts`:
```typescript
export * from "./hello";
```

Our schema is now ready to be exposed via a GraphQL API.

## Exposing the GraphQL API endpoint

While there are many GraphQL server packages available, we need one that will play nicely with Nuxt3's server engine (Nitro / h3). In the spirit of keeping things extensible and framework-agnostic, [GraphQL Helix](https://graphql-helix.vercel.app/) seems like a really good choice. Let's add it to our project:

```shell
yarn add -D graphql-helix
```

With Nuxt3's [auto-discovery of API endpoints](https://v3.nuxtjs.org/docs/directory-structure/server), we can setup the `/api/graphql` endpoint by simply creating the `server/api/graphql.ts` file, and using GraphQL Helix to process the incoming request in the handler function:
```typescript
import { defineHandle, useBody, useQuery } from "h3";
import { getGraphQLParameters, processRequest, renderGraphiQL, sendResult, shouldRenderGraphiQL } from "graphql-helix";
import schema from "../schema";

export default defineHandle(async (req, res) => {
  // Construct GraphQL request
  const request = {
    body: req.method !== "GET" && (await useBody(req)),
    headers: req.headers,
    method: req.method || "GET",
    query: useQuery(req),
  };

  // Render GraphiQL in development only
  if (process.env.NODE_ENV === "development" && shouldRenderGraphiQL(request))
    return renderGraphiQL({ endpoint: "/api/graphql" });

  // Process GraphQL request and send result
  const { operationName, query, variables } = getGraphQLParameters(request);
  const result = await processRequest({
    operationName,
    query,
    variables,
    request,
    schema,
  });
  sendResult(result, res);
});
```

We now have a GraphQL over HTTP specification-compliant server exposing the schema we built with Nexus types.

You may have noticed from the code above that we also get a configured GraphiQL instance at `http://localhost:3000/api/graphql` in development mode, nice!

## Providing the GraphQL execution context

To be useful, our resolvers need access to the Prisma client instance and eventually to the currently logged-in user. These are usually passed to the resolvers in the GraphQL execution context. Fortunately, GraphQL Helix provides an easy way to provide this with full type safety.

First, we'll define our `Context` type and its corresponding context factory function inside `server/context.ts`:
```typescript
import { prisma } from "../prisma/client";

export type Context = {
  prisma: typeof prisma;
};

export async function contextFactory(): Promise<Context> {
  return { prisma };
}
```

Then, back in our handler function inside `server/api/graphql.ts`, we'll pass `contextFactory` to `processRequest` and specify `Context` as its type variable:
```typescript
import type { Context } from "../context";
import { contextFactory } from "../context";

// ...
  const result = await processRequest<Context>({
    // ...
    contextFactory,
  });
// ...
```

Finally, we have to configure Nexus to use our `Context` type in `server/schema.ts` by setting the `contextType` option in `makeSchema` like so:
```typescript
export default makeSchema({
  // ...
  contextType: {
    module: resolve(process.cwd(), "server/context.ts"),
    export: "Context",
  },
}) as unknown as GraphQLSchema;
```

After running `yarn dev` to update `generated/nexus-types.ts`, our resolvers should have access to the fully-typed execution context.

## Projecting the Prisma schema

Defining the exposed GraphQL schema will often involve re-creating our Prisma models using Nexus types, which can be tedious and error-prone. By using the official [Prisma plugin for Nexus](https://nexus.prisma.io/), we can easily project models from the data layer unto GraphQL types in our API layer.

_Please note that `nexus-prisma` is still in early preview, so it should not be used in production right now._

Now that you've been warned, let's add it to our project:
```shell
yarn add -D nexus-prisma
```

We first need to add a `nexus-prisma` generator block in `prisma/schema.prisma`:
```
generator nexusPrisma {
  provider = "nexus-prisma"
}
```

This will generate the available Nexus types directly in `node_modules/nexus-prisma/` on the next `prisma generate`. Similarly to the Prisma client, this package does not work well with ES modules (see this [issue](https://github.com/prisma/nexus-prisma/issues/142)), so until it does we have to re-export all of our models and enums in `prisma/nexus.ts`:
```typescript
import NP, * as NPS from "nexus-prisma/dist-cjs/entrypoints/main";

export const User = NP?.User || NPS?.User;
export const UserRole = NP?.UserRole || NPS?.UserRole;
```

To project the `User` and `UserRole` in our API layer, we add the following in `server/nexus/user.ts` (remember to export it from `server/nexus/_types.ts`):
```typescript
import { enumType, objectType } from "nexus";
import { User, UserRole } from "../../prisma/nexus";

export const UserRoleEnum = enumType(UserRole);

export const UserObject = objectType({
  name: User.$name,
  description: User.$description,
  definition(t) {
    t.field(User.id);
    t.field(User.email);
    t.field(User.role);
  },
});
```

You'll notice in the code above that we omitted the `password` field so it can never be returned by our API.

While this example only covers the basic features of `nexus-prisma`, feel free to read its [documentation](https://nexus.prisma.io/features) to learn more.