In the previous post we started working with data from the Lobsters social news aggregator site in Neo4j Aura with the ultimate goal of building a fullstack application that allows us to explore the data as an interactive graph visualization. In this post we continue our journey toward that fullstack goal, focusing on getting our API layer up and running using Next.js API routes, the Neo4j GraphQL library, Vercel and Neo4j Aura.
We're building this application on the Neo4j livestream so you can check out the video recording to accompany this blog post here:
Interactive Graph Visualization With Neo4j Bloom
Our goal is to build a web application that will demonstrate the power of data visualization when using social network data to, in this case, help us discover interesting and relevant content. Before we dive into building our fullstack application it's worth exploring what sort of graph data visualization tooling is available to us out of the box with Neo4j.
Perhaps the most relevant tool we might consider is Neo4j Bloom. Bloom is included in Neo4j Aura and Neo4j Desktop and allows the user to explore the graph visually without writing Cypher. Bloom is a standalone application that enables users to search for patterns, filter, explore and share graph visualizations.
While Bloom is a powerful tool for use with Neo4j, it's not quite what we want for this project as we want to build a most custom and bespoke experience. There are also other "graph apps" available in Neo4j Desktop, such as the Charts app and Neomap that we can use to build visualizations and dashboards. I covered these in a previous post: "Building An Election Night Dashboard With Neo4j Graph Apps: Bloom, Charts, & Neomap".
Next.js
Next.js is a fullstack React framework built and maintained by Vercel. Next.js includes many features out of the box that we typically need to set up in React applications - things like file-system routing, server side rendering, API routes, etc - which means we can focus on building our application and not boilerplate setup and configuration.
We covered Next.js in a previous blog post so I won't go over all the features now, but I'm a big fan of Next.js and use it with most of my new projects now.
create-next-app
The easiest way to get started with Next.js is to use the create-next-app
CLI. This is a command line tool that enables us to quickly start building a new Next.js application. We can use it to create a new skeleton Next.js project or select from many of the example Next.js projects.
Let's use this to start a new Next.js application in our Lobsters Graph repository:
npx create-next-app next
We can now navigate to the next
directory, and run yarn dev
to start a local web server serving our Next.js application. We should see something like this with some placeholder content:
In this post we're going to focus on building the GraphQL API for our application, rather than the frontend so we won't cover anything React specific today. Instead, we'll be using Next.js' API Routes feature to build our GraphQL API.
Next.js API Routes
Next.js has support for creating API endpoints to add backend functionality to our Next.js application - it really is a fullstack framework after all. To create a new API route we just create a new file in pages/api
that will be mapped to a new API endpoint.
The skeleton Next.js application we created with create-next-app
includes an API route example in pages/api/hello.js
:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
If we make a request to localhost:3000/api/hello
we'll get back a simple JSON object:
{
name: "John Doe"
}
Let's use this API route functionality to add a GraphQL endpoint to our Next.js application.
Creating A GraphQL Server In A Next.js API Route
Following the GraphQL example linked in the Next.js documentation, we'll use the micro
package and apollo-server-micro
to set up a simple GraphQL server as an API route.
First, we'll install the necessary dependencies:
yarn add apollo-server-micro micro graphql
Micro is an HTTP server that works well with Next.js and more importantly for our purposes has an Apollo Server implementation. To create a GraphQL server with Apollo Server we need to create two things: GraphQL type definitions that define the data available in the API, and GraphQL resolver functions that contain the logic for actually resolving GraphQL operations. Apollo Server takes these two inputs, combines them into an executable GraphQL schema and handles the HTTP network layer involved in serving a GraphQL API.
Let's create simple GraphQL type definitions and a single resolver function to get our API up and running as an API route:
import { gql, ApolloServer } from "apollo-server-micro";
const typeDefs = gql`
type User {
id: ID
}
type Query {
getUser: User
}
`;
const resolvers = {
Query: {
getUser: () => {
return {
id: "Foo",
};
},
},
};
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
});
const startServer = apolloServer.start();
export default async function handler(req, res) {
await startServer;
await apolloServer.createHandler({
path: "/api/graphql",
})(req, res);
}
export const config = {
api: {
bodyParser: false,
},
};
We define a single Query field getUser
and a single User
type that has only one field, id
and a resolver function that returns a single hardcoded User
object.
Using GraphQL Playground With Apollo Server v3
In previous versions of Apollo Server by default the GraphQL Playground in-browser tool for exploring GraphQL APIs was available. However, GraphQL Playground has been deprecated for some time now and the latest release of Apollo Server, v3, instead links to the hosted Apollo Studio tool on the "landing page" of the GraphQL API (the page loaded when the GraphQL endpoint is loaded in a web browser).
Apollo Studio is great, but since we want this to be a public GraphQL API that anyone can explore I want GraphQL Playground to be served on the landing page. Fortunately, we can enable GraphQL Playground as a plugin with Apollo Server 3 with these changes:
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
...
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
playground: true,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
});
Now when we load localhost:3000/graphql/api
in a web browser we should see the familiar GraphQL Playground tool. To verify that our GraphQL server is working properly we can run this query:
{
getUser {
id
}
}
And we should see the simple result returned by our getUser
resolver function:
{
data: {
getUser: {
id: "Foo"
}
}
}
Now let's update our GraphQL schema from the placeholder to one that models our Lobsters graph data and works with Neo4j.
Using The Neo4j GraphQL Library
The Neo4j GraphQL library allows us to build Node.js GraphQL APIs backed by Neo4j without writing any resolvers. All we need to do is write GraphQL type definition that define the data model of our database and the Neo4j GraphQL library takes care of the rest - generating a full CRUD GraphQL API and resolvers and translating arbitrary GraphQL operations to database queries.
First, let's install a couple of additional dependencies, the Neo4j GraphQL library and the Neo4j JavaScript driver:
yarn add @neo4j/graphql neo4j-driver
Next, we'll need to create the GraphQL type definitions that map to the property graph model we're using for the Lobsters data. If we refer back to the previous post we can use the graph data model diagram we created using the Arrows.app tool:
The Neo4j GraphQL library uses the following conventions to map GraphQL type definitions to the property graph model:
- GraphQL types map to node labels in the property graph model
- GraphQL scalar fields map to node properties in the property graph model
- GraphQL object and object array fields map to relationships in the property graph model
- The
@relationship
directive is used in the GraphQL type definitions to encode the relationship type and direction in the property graph model
Applying these conventions we end up with the following GraphQL type definitions that map to our Lobsters property graph in Neo4j:
type User {
username: String
created: DateTime
karma: Int
about: String
avatar: String
articles: [Article] @relationship(type: "SUBMITTED", direction: OUT)
invited: [User] @relationship(type: "INVITED_BY", direction: IN)
invited_by: [User] @relationship(type: "INVITED_BY", direction: OUT)
}
type Article {
id: ID
url: String
score: Int
title: String
comments: String
created: DateTime
user: User @relationship(type: "SUBMITTED", direction: IN)
tags: [Tag] @relationship(type: "HAS_TAG", direction: OUT)
}
type Tag {
name: String
articles: [Article] @relationship(type: "HAS_TAG", direction: IN)
}
Now we'll remove the resolver functions from our placeholder GraphQL schema since we don't need to write manual resolvers when using the Neo4j GraphQL library and replace our GraphQL type definitions with the ones we wrote above.
We'll also create a Neo4j JavaScript driver instance to connect to our Neo4j Aura database, using environment variables for the connection credentials and we'll pass our GraphQL type definitions to the Neo4jGraphQL
class constructor to generate our GraphQL API.
We also make use of the @exclude
directive in our GraphQL type definitions to prevent any mutations from being added to the schema - we want this to be a read-only API, at least for now.
import { gql, ApolloServer } from "apollo-server-micro";
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import {Neo4jGraphQL} from "@neo4j/graphql"
import neo4j from "neo4j-driver"
import 'ts-tiny-invariant' // importing this module as a workaround for issue described here: https://github.com/vercel/vercel/discussions/5846
const typeDefs = gql`
type User @exclude(operations: [CREATE, UPDATE, DELETE]) {
username: String
created: DateTime
karma: Int
about: String
avatar: String
articles: [Article] @relationship(type: "SUBMITTED", direction: OUT)
invited: [User] @relationship(type: "INVITED_BY", direction: IN)
invited_by: [User] @relationship(type: "INVITED_BY", direction: OUT)
}
type Article @exclude(operations: [CREATE, UPDATE, DELETE]) {
id: ID
url: String
score: Int
title: String
comments: String
created: DateTime
user: User @relationship(type: "SUBMITTED", direction: IN)
tags: [Tag] @relationship(type: "HAS_TAG", direction: OUT)
}
type Tag @exclude(operations: [CREATE, UPDATE, DELETE]) {
name: String
articles: [Article] @relationship(type: "HAS_TAG", direction: IN)
}
`;
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
)
const neoSchema = new Neo4jGraphQL({typeDefs, driver})
const apolloServer = new ApolloServer({
schema: neoSchema.schema,
playground: true,
introspection: true,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
});
const startServer = apolloServer.start();
export default async function handler(req, res) {
await startServer;
await apolloServer.createHandler({
path: "/api/graphql",
})(req, res);
}
export const config = {
api: {
bodyParser: false,
},
};
Next.js supports setting environment variables using .env
files, so let's create a .env.local
file where we'll add the credentials for our Neo4j Aura instance. We'll also set the DEBUG
environment variable to enable debug logging for the Neo4j GraphQL library. This will log the generated Cypher queries among other things, which can be helpful to understand how the database queries are generated from GraphQL operations.
NEO4J_USER=neo4j
NEO4J_URI=neo4j+s://YOUR NEO4J AURA URI HERE
NEO4J_PASSWORD=YOUR NEO4J AURA PASSWORD HERE
DEBUG=@neo4j/graphql:*
We now have a GraphQL endpoint running locally at localhost:3000/api/graphql
that we can use to fetch data from our Neo4j Aura database using GraphQL. Next, we'll deploy our Next.js application on Vercel so that it will be publicly accessible.
Deploying To Vercel
Vercel is a cloud platform that we'll use to build and deploy our Next.js application. The frontend React application (once we build it!) will be built and hosted on a CDN and our GraphQL API route will be automatically deployed as a serverless function.
Vercel integrates with GitHub so once we commit our changes we can add our Next.js application by selecting the GitHub repository in Vercel. Because we're using a bit of a monorepo setup and our Next.js application is not in the root directory of the repository we just need to tell Vercel that the root directory of our Next.js application is the next
directory. We'll also add our Neo4j Aura connection credentials as environment variables, setting values for NEO4J_PASSWORD
, NEO4J_URI
, and NEO4J_USER
in the Vercel project configuration.
Once we've added our project the Vercel build service will pull down our code from GitHub, build the project and deploy our Next.js application (static content to a CDN and our GraphQL API to a serverless function). Our project is automatically assigned a domain and SSL certificate! Because we connected our project via GitHub any commits and pull requests will trigger another build. Each build is assigned its own unique URL which means pull requests will be built and deployed as a "preview build" which we can test and share before deploying to our main domain. This is really great collaboration feature.
Since we added the GraphQL Playground plugin we can navigate to our Vercel project's URL in the browser https://lobste-rs-graph.vercel.app/api/graphql
and test our GraphQL endpoint:
Now that we've got our GraphQL API up, running, and deployed on Vercel in the next post we'll start building out the frontend application, taking a look at graph data visualization in React with GraphQL. Be sure to subscribe to my newsletter to be notified when the next post is ready!
Top comments (9)
Thanks for sharing
I want to give it a try man, I was 2 days ago trying to upload my graphql server to vercel but with no success I ended up going to heroku.
I noticed you have everything inside your api endpoint, but if you decided to have everything organized in different files and folders, would you keep all those files and folders inside the api folder ?
Hi Pako - what issues did you run into?
Yes, you can use whatever file strucutre makes sense and import as modules as necessary, and still leverage Next.js's file-based router convention (so in pages/api/foobar.js just import as modules any lib type code that you want to use).
So my first thought was:
Vercel basically allows you to deploy compiled frontend apps, so maybe I would need to compile the playground to present it as my frontend app and all my routes move them to lambdas.
But I didn't know in what direction to go so I just went with Heroku.
2 years ago I took Wes Bos' advanced react course with graphql and I was able to deploy the app in vercel, but that was before they added the new /api file structure.
Anyways, I'll try to update my code and upload it to vercel and if I have some problems deploying I might reach out for help :P
Gotcha. You can serve GraphQL Playground from a Next.js API Route. In Apollo Server 3 you'll just need to enable it via a plugin: apollographql.com/docs/apollo-serv...
I am trying to implement that with jwt, so I used a plugin
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground,
{
auth: new Neo4jGraphQLAuthJWTPlugin({
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}),
},
],
the token doesn't applied, as well as I am sending it within the headers
Hi William! Amazing post. It solved so much doubts I had :) thanks!
What do you think about this issue with Next.js, Micro and ApolloServer to run a GrapqhQL on a server less environment?
github.com/apollographql/apollo-se...
The next episode will be a lot of fun.
Apologies - this is my first foray into Next so I'm probably making a complete blunder.
I followed your video example and all was well until integrating the Neo4J graphql. Everything compiles etc but when I try getting the schema from Postman/BananaCakePop I get a server 500 error and the following trace in my terminal window:
error - Error: Apollo Server requires either an existing schema, modules or typeDefs
I can't see where I'm going wrong as my graphql.js appears identical to your own apart from a simple 'UserEntity' typedef rather than the article, tag and user types you define.
Any help gratefully received as I'm stumped right now.
Cheers!
Luckily, the Neo4J chaps over on Discord pointed me in the right direction to some recent changes for async:
neo4j.com/docs/graphql-manual/curr...
Hope this helps someone.