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.ts23import { makeSchema, queryType } from 'nexus'4import { ApolloServer } from 'apollo-server-micro'56const Query = queryType({7 definition(t) {8 t.string('hello', { resolve: () => 'hello world!' })9 },10})1112const schema = makeSchema({13 types: [Query],14})1516const server = new ApolloServer({17 schema,18})1920export const config = {21 api: {22 bodyParser: false,23 },24}2526export 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 hello3}
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.ts23import { objectType, queryType } from 'nexus'45export const Framework = objectType({6 name: 'Framework',7 definition(t) {8 t.id('id')9 t.string('name')10 },11})1213export 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.ts23import { makeSchema } from 'nexus'4import * as QueryTypes from './Query'56const schema = makeSchema({7 types: [QueryTypes],8})910export 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.
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],- })1516 const server = new ApolloServer({17 schema,18 })1920 export const config = {21 api: {22 bodyParser: false,23 },24 }2526 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 id4 }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.tsx23import { request, gql } from 'graphql-request'45export async function getServerSideProps() {6 const query = gql`7 {8 frameworks {9 id10 name11 }12 }13 `14 const data = await request('http://localhost:3000/api/graphql', query)15 const { frameworks } = data16 return {17 props: {18 frameworks,19 },20 }21}2223export 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 thegetServerSideProps
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
.
1import { makeSchema } from 'nexus'2import * as QueryTypes from './Query'+import path from 'path'45const 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})1213export 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.
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: