How to setup a GraphQL server in Next.js with API endpoints
using TypeScript, type-graphql and apollo-server-micro
I have been learning GraphQL for a while now, and I love Next.js for the benefits it comes with. So, one morning, I decided to try and combine the two and see if I can make it work. The journey was not as smooth as I expected it to be, so I decided to document my approach. Specifically, I had two issues:
- You need to get your configurations correct to make decorators play along nicely.
- You need to have a specific way of creating database connections to not clash with Next.js’ Hot Module Reloading (HMR).
The aim of this article is to give you a boilerplate that will work for Next.js and GraphQL combined without any issues.
Disclaimer: This article assumes some knowledge about TypeScript, Next.js and API routes, GraphQL, PostgreSQL database and TypeORM.
Creating a new Next.js App
To start things off, we create a new Next.js app. I will be using yarn
, but the same should be achievable with npm
as well.
mkdir nextjs-graphql
cd nextjs-graphql
yarn create next-app . --typescript
The above series of commands will create a basic Next.js template app with TypeScript in the nextjs-graphql
directory.
I will make the folder structure a bit more manageable by deleting a few files, but this is optional. This is how my folder structure looks like after deleting some of the files:
tree . -L 2 -I node_modules.
├── next.config.js
├── next-env.d.ts
├── package.json
├── pages
│ └── _app.tsx
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── README.md
├── tsconfig.json
└── yarn.lock
Installing dependencies
Let’s start off by installing the dependencies that we need.
yarn add graphql apollo-server-micro reflect-metadata type-graphql class-validator
graphql
is self-explanatory.apollo-server-micro
is the package that will allow us to instantiate anApolloServer
instance.type-graphql
is a framework for building GraphQL API using TypeScript. It has a dependency onreflect-metadata
andclass-validator
.
Setting up a resolver and API endpoint
If you are not familiar with type-graphql
, I suggest reading up on their amazing documentation. If you want to learn more about type-graphql
, I recommend Ben Awad’s tutorial series on type-graphql
.
We will create a hello world resolver to test things out with our GraphQL API. type-graphql
includes some decorators to annotate classes which will let GraphQL know about the schema and will enable type safety in the queries. Create a file HelloWorldResolver.ts
under /lib/serverless/graphql/resolvers
.
As you can see, we are importing Resolver
and Query
decorators to annotate our resolver class. And for now, we are simply returning a string from the resolver.
At this point, your IDE should be complaining about decorators and how you need to enable a certain feature to resolve the warnings. We will deal with that later, because that is only a fragment of the issues we need to deal with. For now, we want to setup our API handler for GraphQL. Create a file index.ts
under /pages/api/graphql
.
We are doing a few things here:
- Import
reflect-metadata
package at the very top. If you have usedTypeORM
before, you would be familiar with this. - We are importing
ApolloServer
fromapollo-server-micro
- We are importing
buildSchema
fromtype-graphql
, which will allow us to build schema definitions directly from our annotated resolvers. Hence, we do not needgql
template strings for type definitions. - We are disabling
bodyParser
of Next.js API endpoint, so that GraphQL is the one handling responses from the route. - Finally, after instantiating our
ApolloServer
instance, we are exporting, by default, the GraphQL handler for this particular API endpoint. So, when you want to perform queries, you would need to send them tohttp://localhost:3000/api/graphql.
At this point, you should see another error, saying that you cannot use top-level await. Now, we will deal with resolving those issues.
Resolving the configuration issues
Even if you try to launch the application using yarn dev
right now, you won’t be able to reach the GraphQL endpoint. Because, we need to take care of some configuration before our TypeScript files can be properly transpiled and bundled by webpack. Next.js by default doesn’t have support for decorators enabled. There are four steps to this:
- Modifying
tsconfig.json
We need to explicitly say that we want decorator support in our app. So, add the following lines in your tsconfig.json
, under compilerOptions
:
"target": "es2018",
"lib": ["dom", "dom.iterable", "esnext", "esnext.asynciterable"],
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
- Installing a few dependencies
yarn add -D @babel/core @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties babel-plugin-transform-typescript-metadata
- Creating a
.babelrc
file
Create a .babelrc
file at the root directory of your app and enable the support for decorators. Note that the ordering of the plugins matter in this case. This has been mentioned in this particular GitHub issue on Next.js repository.
{
"presets": ["next/babel"],
"plugins": [
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
- Modifying webpack configuration in
next.config.js
You also need to enable top-level await in your webpack config, which should be done in your next.config.js
module.exports = {
reactStrictMode: true,
webpack: function (config, options) {
config.experiments = { topLevelAwait: true }
return config;
}
}
After these steps, you should be able to reach the GraphQL API endpoint at http://localhost:3000/api/graphql
after running yarn dev
, and you should be able to see the GraphQL playground and run queries it.
Practical Example
Let’s extend our endpoint further by using a real world example of user registration and see if it can withstand a real world use-case.
Setting up a database
Let’s setup a postgres database locally. I will be using the commands listed in this medium.com article.
sudo -u postgres psql
create database nextjs_graphql;
create user nextjs with encrypted password 'nextjs';
grant all privileges on database nextjs_graphql to nextjs;
With the database created, you can now create a database connection. Let’s create a file db.ts
in /lib/serverless/utils/
folder. I will be referring to this article which explains how to properly create a database connection that doesn’t clash with Next.js’ Hot Module Reloading.
Let’s not forget to install the necessary packages:
yarn add typeorm pg
Note that we will create the User.ts
file shortly.
And now, you can modify the index.ts
file under /pages/api/graphql/
. You can pass the database connection object to your resolvers through the context
. However, we will not be using it in this article, but it’s left here to serve as an example.
Note that we will be creating the UserResolver.ts
file shortly.
Let’s create a file User.ts
under /lib/serverless/entities/
. We will be using a lot of decorators in this one.
Let’s see what’s going on here:
- First, we extend from the
BaseEntity
class oftypeorm
. This allows us to readily call methods likeuser.create()
oruser.find()
without requiring to obtain aconnection
object or arepository
. - We use the decorators from
typeorm
to convert the class into a database entity. We use the@Entity
and@Column
decorators for this. - Next, we need to be able to specify that this class will also be used as a GraphQL object type, which is why we are also using the decorators from
type-graphql
, namely@ObjectType
and@Field
decorators.
Finally, we need to write the resolver for the User
entity, which we will do in the UserResolver.ts
file under /lib/serverless/graphql/resolvers/
.
We have a mutation registerUser
and a query getUser
. They take their corresponding input parameters to save a user to the database and return a user from the database, if any, respectively.
Just to save some time, I will not be hashing the password before saving it to the database, but it is recommended that you hash any sensitive information before storing it in a database.
And with that, we are done setting up. This is the final folder structure of the project after all the modifications above:
.
├── lib
│ └── serverless
│ ├── entities
│ │ └── User.ts
│ ├── graphql
│ │ └── resolvers
│ │ ├── HelloWorldResolver.ts
│ │ └── UserResolver.ts
│ └── utils
│ └── db.ts
├── next.config.js
├── next-env.d.ts
├── package.json
├── pages
│ ├── api
│ │ └── graphql
│ │ └── index.ts
│ └── _app.tsx
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── README.md
├── tsconfig.json
└── yarn.lock
You can hop onto your GraphQL playground at http://localhost:3000/api/graphql
and verify that the queries and mutations are working. I have also tested this in production environments using yarn build && yarn start
to verify that nothing breaks in production.
The entire code for the article can be found in this repository. Let me know your thoughts and feedback.
Go Beyond!