Adoption Guides

Next.js Users

Nexus in a Next.js Project

Note: Here is a Codesandbox for the example that we'll build incrementally in this article.

Next.js comes with a feature called API Routes. With API Routes, we can easily create dedicated endpoints to use in our Next.js applications. These endpoints can then be deployed as serverless functions.

In this guide, we'll see how to work with Next.js API Routes to serve a Nexus GraphQL API and then consume it from the front end.

Getting Started

Let's start by creating a new Next.js application. If you already have a Next.js app, you can ignore this step.

$npm init next-app
$# Provide a name for your app

After installation, we need to opt-in to use TypeScript. To do this, let's install typescript and @types/react and @types/node.

$npm i -D typescript @types/react @types/node

To finish out the TypeScript setup, run the app in development mode. This will populate the tsconfig,json file.

$npm run dev

For more information on using TypeScript in Next.js, check out the docs.

With the Next.js project in place, we now need to install the other dependencies we'll need.

In addition to Nexus, we'll need a server to run the GraphQL API. Nexus works with a range of GraphQL servers. We'll use Apollo Server and opt for the apollo-server-micro variety as it is well-suited to serverless environments.

$npm i nexus apollo-server-micro

Create an API Route

Next.js API routes work on a folder-based convention. Any TypeScript (or JavaScript) file found under the pages/api directory will become an API route.

If we wanted to approximate a REST API or otherwise use JSON data endpoints, we could create one file per endpoint in this directory. However, since we're using GraphQL, we just need a single endpoint and, consequently, a single file in this directory.

Create a file under pages/api called graphql.ts.

$touch pages/api/graphql.ts

Next.js API routes typically export handler function that approximates an express endpoint. The function has NextApiRequest and NextApiResponse parameters which are used to read information about the incoming request and respond appropriately.

For our GraphQL-based API route, we need to adjust this function slightly. Instead of exporting a handler function directly, we'll call the createHandler method on our instance of Apollo Server.

Populate the graphql.ts file with a simple schema and server. Then, expose the server at a path of /api/graphql.

1// pages/api/graphql.ts
2
3import { makeSchema, queryType } from 'nexus'
4import { ApolloServer } from 'apollo-server-micro'
5
6const Query = queryType({
7 definition(t) {
8 t.string('hello', { resolve: () => 'hello world!' })
9 },
10})
11
12const schema = makeSchema({
13 types: [Query],
14})
15
16const server = new ApolloServer({
17 schema,
18})
19
20export const config = {
21 api: {
22 bodyParser: false,
23 },
24}
25
26export default server.createHandler({
27 path: '/api/graphql',
28})

Now if we navigate to http://localhost:3000/api/graphql, we should see the GraphQL Playground and we should be able to make a query for the 'hello world!' message.

1{
2 hello
3}

Create a Schema

When creating a schema with Nexus, we have the option of housing all the parts of the schema in a single file or we can split it out into many files. In either case, it's tempting to place these files near the graphql.ts API route. However, since Next.js will create an API route for every file in the pages/api directory, we should take care not to do this.

It's best to keep all the parts of your schema in a separate directory. Most often, this will be a directory at the root of your project.

Create a diretory called schema at the project root.

$mkdir schema

Let's start by creating a schema to retrieve some data with the queryType.

In the schema directory, create a file called Query.ts.

$touch schema/Query.ts

At the very least, this file should export a queryType that can be used in our schema.

Let's create and export a Query constant, as well as a Framework object type to represent a set of front end technologies.

1// schema/Query.ts
2
3import { objectType, queryType } from 'nexus'
4
5export const Framework = objectType({
6 name: 'Framework',
7 definition(t) {
8 t.id('id')
9 t.string('name')
10 },
11})
12
13export const Query = queryType({
14 definition(t) {
15 t.list.field('frameworks', {
16 type: 'Framework',
17 resolve: () => {
18 return [
19 {
20 id: '1',
21 name: 'React',
22 },
23 {
24 id: '2',
25 name: 'Vue',
26 },
27 {
28 id: '3',
29 name: 'Angular',
30 },
31 {
32 id: '4',
33 name: 'Svelte',
34 },
35 ]
36 },
37 })
38 },
39})

Let's now create an index file in the schema directory which will be responsible for importing all the types in the directory and exporting a constructed schema using makeSchema.

$touch schema/index.ts

In the index.ts file, bring in makeSchema as well as the types we just created in Query.ts.

1// schema/index.ts
2
3import { makeSchema } from 'nexus'
4import * as QueryTypes from './Query'
5
6const schema = makeSchema({
7 types: [QueryTypes],
8})
9
10export default schema

Now in the graphql.ts for our API route, we can simply pull in the constructed schema and serve it with Apollo Server.

Diff
Code
1 // pages/api/graphql.ts
- import { makeSchema, queryType } from 'nexus'
3 import { ApolloServer } from 'apollo-server-micro'
+ import schema from '../../schema'
5
- const Query = queryType({
- definition(t) {
- t.string('hello', { resolve: () => 'hello world!' })
- },
- })
-
- const schema = makeSchema({
- types: [Query],
- })
15
16 const server = new ApolloServer({
17 schema,
18 })
19
20 export const config = {
21 api: {
22 bodyParser: false,
23 },
24 }
25
26 export default server.createHandler({
27 path: '/api/graphql',
28 })

If we head back over to localhost:3000/api/graphql, we can see results for our call for frameworks.

1{
2 frameworks {
3 id
4 }
5}

Call the GraphQL API from the Front End

In a Next.js app, we have several options for producing our pages:

  • Server-side rendering using the getServerSideProps function
  • Static site generation using the getStaticProps function
  • Client-side rendering

No matter how we want to produce our pages, we can call our API to get data to display.

If we would like to use server-side rendering, we can use a library like graphql-request to make an API call.

Start by intalling the graphql-request library.

$npm i graphql-request

Create a function called getServerSideProps and make a call to our GraphQL API. The returned data can be supplied in the returned object which will make it available as a prop in the function that produces the page. Next.js will take care of treating this page as a server-side rendered page.

Rename the module pages/index.js to pages/index.tsx and replace with the following.

1// pages/index.tsx
2
3import { request, gql } from 'graphql-request'
4
5export async function getServerSideProps() {
6 const query = gql`
7 {
8 frameworks {
9 id
10 name
11 }
12 }
13 `
14 const data = await request('http://localhost:3000/api/graphql', query)
15 const { frameworks } = data
16 return {
17 props: {
18 frameworks,
19 },
20 }
21}
22
23export default function Home({ frameworks }) {
24 return (
25 <div>
26 <ul>
27 {frameworks.map(f => (
28 <li key={f.id}>{f.name}</li>
29 ))}
30 </ul>
31 </div>
32 )
33}

When the page renders, we'll see a list of the frameworks.

Note: Using graphql-request to make a server-side call for data from our GraphQL API is good for initially fetching the data but it won't do much for us if we want to have client-side GraphQL features such as caching. If we want additional features like those provided by Apollo Client, it won't work to mix the data retrieved in the getServerSideProps function with data managed on the client by Apollo.

SDL and Type Generation

Nexus generates a GraphQL SDL file and TypeScript typings by default. You can customize their location by configuring outputs option in makeSchema. In a typical project, we can tell Nexus where to place these files by using __dirname to define a path. That might looks something like this:

1makeSchema({
2 outputs: {
3 schema: path.join(__dirname, '/generated/schema.graphql'),
4 typegen: path.join(__dirname, '/generated/types.ts'),
5 },
6 // ...
7})

Using __dirname in this fashion will not work in a Next.js project. Next.js takes control of __dirname and overrides it with special behavior which interferes with our ability to use it as we normally would.

The solution is to use path.join and process.cwd together to describe where to place the generated files.

In the schema/index.ts file, configure makeSchema to output the generated SDL and typings using path.join and process.cwd.

Diff
Code
1import { makeSchema } from 'nexus'
2import * as QueryTypes from './Query'
+import path from 'path'
4
5const schema = makeSchema({
6 types: [QueryTypes],
+ outputs: {
+ typegen: path.join(process.cwd(), 'generated/nexus-typegen.ts'),
+ schema: path.join(process.cwd(), 'generated/schema.graphql'),
10 }
11})
12
13export default schema;

Finally we will setup our build script to run Nexus typegen before Next.js tries to build. This ensures that we get the full type safety of Nexus when our app builds prior to deployment.

Diff
Code
1{
2 "name": "my-app",
3 "version": "0.1.0",
4 "private": true,
5 "scripts": {
6 "dev": "next dev",
7+ "build:nexus-typegen": "ts-node --compiler-options '{\"module\":\"CommonJS\"}' --transpile-only schema",
+ "build": "npm run build:nexus-typegen && next build",
9- "build": "next build",
10 "start": "next start"
11 },
12 "dependencies": {
13 "nexus": "^0.19.2",
14 "apollo-server-micro": "^2.19.0",
15 "graphql-request": "^3.3.0",
16 "next": "10.0.3",
17 "react": "17.0.1",
18 "react-dom": "17.0.1"
19 },
20 "devDependencies": {
21 "@types/node": "^14.14.10",
22 "@types/react": "^17.0.0",
23 "typescript": "^4.1.2"
24 }
25}

Additional Resources

For more on how to integrate Nexus into a Next.js project, check out these resources:

Edit this page on Github