So, you have a Next.js TypeScript project that needs to expose an API for your clients to consume. You don't want to provide a REST API for some reason. Then, welcome to GraphQL!
If you are a seasoned developer, you can jump to the code if you want.
Can I Serve GraphQL Endpoint in Next.js?
Expose GraphQL using Next.js API Router
The Next.js API router can provide a good start to building a GraphQL API. To build GraphQL API using Apollo Server in Next.js, you can install
@apollo/server
, @as-integrations/next
, and graphql-tag
library and provide schema and type definitions. Wire up those tools, and you can start consuming the API right away using the single /api/graphql
endpoint.In this tutorial article, you will learn how to build a Typescript based Apollo GraphQL server that acts as your API endpoint using the Next.js API Router.
Host GraphQL using Vercel Edge Functions
The best thing about Next.js is that your GraphQL endpoint will be served from Edge Functions by Vercel if you host the application on their platform.
You will learn quickly in this article. So read on!
What Will You Learn in this Tutorial?
In this article, you will learn how to build a GraphQL API in your Next.js project that clients can consume.
What Are You Building?
Nowadays, people still read blog articles, just like you're doing now. There are many reasons for this, of course.
A good blog provides mechanisms for the client app to fetch all blogs, get blog details, query blogs by title, and favorite a blog post. Like Mozzlog 😁.
The post contains a title, cover image, categories, last modification timestamp, number of reads, and content.
List of Topics That You Will Cover
You will cover the following topics:
- Prerequisites
- Setting up the schema
- Fake Initial Data
- Preparing Resolvers
- Setting up the Server
- Exposing GraphQL to the World
- Use cURL to Test the Live GraphQL Endpoint
- Performing Mutations
- Sandbox Test
Yes, you will end up with a sandbox test so that you can test the API via the browser without spinning up another GraphQL client application.
A Sandbox Test makes testing GraphQL endpoints easier. Wouldn’t you want to help your client try your endpoint? It can make adoption faster.
Let's get to work. Are you ready to build a blog's GraphQL API?
Prerequisites
This article assumes that you are seasoned or have experience with Next.js. Therefore, you won't need to read about how to set up a Next.js project.
Of course, you'll need a running Next.js project. Just ensure that you have TypeScript set up because you will use the language to build the GraphQL API.
This project will use Apollo GraphQL. To install the dependencies, you can use this command:
npm install @apollo/server @as-integrations/next graphql-tag
You will use
@apollo/server
to serve your GraphQL resolver and schema, while @as-integrations/next
will provide functions to serve your ApolloServer in Next.js. The @graphql/tag
role is to write the schema of your GraphQL query.Setting Up The Schema
First, you need to build a schema for the Blog GraphQL API. The schema should address the questions you want to answer with the API.
- Clients want to see a list of blog posts.
- Clients want to query a blog by its title.
- Clients want to mark a post as a favorite.
- Clients want to view the details of a blog post.
You need to define the schema and resolvers in variables. You can start by defining the Post type.
Post Type GraphQL Schema
type Post { id: ID! slug: String! title: String! categories: [String!]! cover: String lastUpdatedAt: Float! isFavorite: Bool content: String! }
The fields you can include in your Post type are
id
, slug
, title
, categories
, cover
, lastUpdatedAt
, and content
.Query Type GraphQL Schema
type Query { getPost(id: ID!): Post getPostsByTitle(query: String): [Post!]! getAllPosts: [Post!]! }
The fields you can include in your
Query
type are getPost
, which accepts the id
parameter, and getAllPosts
, which returns all Post
objects.That's it. Creating a GraphQL type is easy. Now, let's put those types in a variable called
schemaDefinitions
.Create schemaDefinitions
import { gql } from 'graphql-tag'; const schemaDefinitions: DocumentNode = gql` type Post { id: ID! slug: String! title: String! categories: [String!]! cover: String date: String! published: Boolean! lastEditedAt: Int! isFavorite: Bool content: String! } type Query { getPost(id: ID!): Post getPostsByTitle(query: String): [Post!]! getAllPosts: [Post!]! } `;
In the above code, the
gql
function will convert our string into a DocumentNode
instance.You can omit the explicit type annotation from
schemaDefinitions
if you want. TypeScript will infer the type from the gql
return type.Fake Initial Data
In this project, you won't be reading from a database. That will be covered in another article, where you'll read from databases like Postgres, MongoDB, or Redis.
You also won't be reading from a
.json
file, as that would be another topic involving serialization, deserialization, and saving to a file in TypeScript. However, if you need that, you can refer to this article.To simplify the tutorial, you'll create a constant variable that stores your list of posts in memory.
Yes, you and I like constant variables. Make everything much easier.
Let's generate our posts using the code below:
const posts: Post[] = [ { id: "1", slug: "golang-files-tutorial-beginner", title: "Quick Golang Files Tutorial for Beginner", cover: null, categories: [ "Golang", "Snippet" ], lastEditedAt: 1692584880000, isFavorite: null, content: "golang content" }, { id: "2", slug: "restapi-requests-in-python", title: "Quick REST API Calls using the Requests Library in Python", cover: null, categories: [ "Python", "RestAPI" ], lastEditedAt: 1692656820000, isFavorite: null, content: "python content" }, { id: "3", slug: "react-pdf-module-parse-failed-next-js", title: "How to Resolve the react-pdf Issue: \"Module parse failed: Unexpected character '�' (1:0)\" in Next.js", cover: null, categories: [ "Next.js" ], lastEditedAt: 1692612960000, isFavorite: null, content: "react-pdf content" } ];
Preparing Query
Resolvers
In GraphQL, the function that runs when you query data is called a resolver.
To prepare resolvers, you can use this code:
const resolvers = { Query: { getPost: async (parent: any, args: { id: string }) => { const { id } = args; return posts.find((post: Post) => post.id === id) }, getPostsByTitle: async (parent: any, args: { query: string }) => { const { query } = args; return posts.find((post: Post) => post.title.includes(query)) }, getAllPosts: async () => { return posts }, }, };
In the above code, you'll only need a resolver for the
Query
as it is the schema that exposes functions to get the list of posts.- In
getPost
, you match posts by comparing theirid
to the given argument and returning the matched one.
- For
getPostsByTitle
, you find posts withtitle
containing the providedquery
and return them.
getAllPosts
simply returns all post content without any filtering.
Setting Up GraphQL Server
As a prerequisite, you've installed Apollo GraphQL to act as your GraphQL engine. To set up Apollo Server, you can use this code:
import { ApolloServer } from '@apollo/server'; const resolvers = ... // Your resolvers const schemaDefinitions = ... // Your schema definitions const server = new ApolloServer({ resolvers, schemaDefinitions, });
Exposing GraphQL Next.js Endpoint
GraphQL is a mechanism that allows you to define how the response should look, rather than the server. It can save you a lot of data when you need a small amount of information displayed.
To transport your GraphQL query, you can leverage HTTP POST.
In this project, you won't set up the transport yourself. You'll use the
@as-integrations/next
package you installed earlier.import { startServerAndCreateNextHandler } from '@as-integrations/next'; const server = ... // Your ApolloServer export default startServerAndCreateNextHandler(server);
Now that everything is in place, you can run the Next.js project using:
npm run dev
When your application is accessible through
localhost:3000
, you can access the GraphQL endpoint at http://localhost:3000/api/graphql
.Use cURL to Test the Live GraphQL Endpoint
Get All Posts
Now that everything is in place, you can test the GraphQL endpoint. Use this cURL command to get a list of blog posts:
curl -X POST \ -H "Content-Type: application/json" \ -d '{ "query": "query { getAllPosts { id slug title categories } }" }' \ http://localhost:3000/api/graphql
That will give you response like this:
{ "data": { "getAllPosts": [ { "id": "1", "slug": "golang-files-tutorial-beginner", "title": "Quick Golang Files Tutorial for Beginner", "categories": [ "Golang", "Snippet" ] }, { "id": "2", "slug": "restapi-requests-in-python", "title": "Quick REST API Calls using the Requests Library in Python", "categories": [ "Python", "RestAPI" ] }, { "id": "3", "slug": "react-pdf-module-parse-failed-next-js", "title": "How to Resolve the react-pdf Issue: \"Module parse failed: Unexpected character '�' (1:0)\" in Next.js", "categories": [ "Next.js" ] } } }
Get Specific Post by ID
To retrieve a specific
Post
, you can use getPost
from the Query Schema. You need to pass the Post.id
to get the information you want.Use this cURL command to get a specific post:
curl -X POST \ -H "Content-Type: application/json" \ -d '{ "query": "query { getPost(id: \"1\") { id slug title categories cover content } }" }' \ http://localhost:3000/api/graphql
It will result in this response:
{ "data": { "getPost": { "id": "1", "slug": "golang-files-tutorial-beginner", "title": "Quick Golang Files Tutorial for Beginner", "categories": [ "Golang", "Snippet" ], "cover": null, "content": "golang content" } } }
Isn't that awesome?
But how do you update data using GraphQL? Let's say you want to favorite a post. In that case, let's introduce mutations.
Performing Mutations
When you want to favorite a post, you must update the database via mutation.
GraphQL provides a specific schema to explicitly tell the server that a client wants to perform a mutation.
Mutation Schema
To enable clients to perform mutations, you should update the
schemaDefinitions
to include the mutation.Add this to your
const schemaDefinitions
above:type Mutation { addPostToFavorite(id: ID!): Post }