Creating a Next.js 13 FullStack Application with GraphQL, TypeScript, Prisma, and Vercel — Part 2

Alois Guitton
7 min readOct 24, 2023

--

Building a simple GraphQL server

An API route in Next.js provides a way to build an API directly into a Next.js app. It provides a solution to serverless functions in JavaScript. The API Routes feature is like an express.js-like solution enabling you to build an API endpoint within a Next.js app. You can use the /api directory to create these routes.

Here is how you can set up a simple GraphQL server in Next.js 13:

  1. Install the necessary dependencies into your project :
npm install @as-integrations/next @apollo/server graphql
npm install --save-dev apollo @types/graphql
  1. Create GraphQL Server: Firstly, import needed libraries and define your GraphQL schema and resolvers in ‘/pages/api/route.ts', like so:
import { ApolloServer } from "@apollo/server";
import { NextRequest } from "next/server";
import { startServerAndCreateNextHandler } from "@as-integrations/next";

const typeDefs = `#graphql
type Query {
Hello: String
}
`;
const resolvers = {
Query: {
Hello: () => "Word!",
},
};
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
});
const handler = startServerAndCreateNextHandler<NextRequest>(apolloServer);
export async function GET(request: NextRequest) {
return handler(request);
}
export async function POST(request: NextRequest) {
return handler(request);
}

In this case, Hello is a Query type that returns a string. This Resolver function corresponds to this Query.

  1. Now you’re ready to test your API route! Start your server with npm run dev, and navigate to http://localhost:3000/api/graphql. You should see the Apollo Server playground. You can then query your API like so:
query ExampleQuery {
Hello
}

You should see “Word!” return as a response.

You can find the corresponding source code with tag “Part2.1” here.

Introduction to Prisma and it’s role in data management

Prisma is a powerful and versatile open-source database toolkit that simplifies the process of working with databases. It acts as an interface between your application and the database, providing a type-safe query builder and an auto-generated, type-safe data access layer.

Prisma offers an abstraction layer that allows you to write database queries using a type-safe API. It supports various databases such as PostgreSQL, MySQL, SQLite, and SQL Server, providing you with the flexibility to choose the database that best suits your application’s needs.

One of the significant advantages of using Prisma is that it seamlessly integrates with GraphQL. Prisma’s GraphQL API automates the process of fetching and updating data from the database through GraphQL queries and mutations. It eliminates the need to write complex SQL queries manually, reducing development time and potential errors.

Here are some key benefits and features of Prisma:

  1. Type Safety: Prisma generates TypeScript or JavaScript code based on your database schema, providing type-safe queries and eliminating runtime errors due to invalid queries.
  2. Database Migrations: Prisma allows you to manage database schema changes in a structured and organized

Integrating Prisma service with GraphQL

Prerequisites

To start, we have to install Prisma and configure it. For this tutorial, my database is a cockroachdb. If you use another provider, you can change it (list of providers).

npm install prisma --save-dev
npx prisma init --datasource-provider cockroachdb

Now, we have to configure our database connection. For that, prisma has created a .env file and DATABASE_URLenvironment variable. Here, this variable will be available only in server side. To create environment variable for client side, variables should be prefixe with NEXT_PUBLIC_[variable-name] (Next.JS documentation). We can change this DATABASE_URL to use your own url. For me it will be :postgresql://tutorial_user:NOnalIFyPUILKSZzh×6uAg@poodle-chimera-11062.8nj.cockroachlabs.cloud:26257/defaultdb

Configure our database schema

First, let’s define our Prisma data model in prisma/schema.prisma. In this case, we're gonna make a simple Todo model:

model Todo {
id Int @id @default(sequence())
title String
done Boolean @default(false)
createdAt DateTime @default(now())
}

Your file will be :

To send this new schema in our database and update it, we have to launch npx prisma migrate dev --name initcommand. The response should be :

Now Prisma Client can be imported in your application. We will create a new folder named lib into src/app. The libfolder in a Next.JS 13 application is a common convention among developers as a place to store utilities, shared functions, and any form of reusable logic that isn't a component.

In this folder, we create an subfolder named prisma and a index.ts file with this content :

import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient({
log: ["warn", "error", "query"],
});
export default prisma;

This will allow us to use prisma to query our database.

Integrating Prisma service with GraphQL

In GraphQL, queries and mutations are two different types of operations. Queries are used to fetch or retrieve data, they are read-only operations. They don’t change or manipulate the data on the server.

On the other hand, mutations are write operations. They are used to create, update, or delete data on the server. In essence, queries are akin to the GET method in REST, while mutations resemble POST, PUT, and DELETE methods.

TypeDefs

When using GraphQL in your backend, creating a schema to define your data types, queries, mutations, is essential. Definition of these types is done through TypeDefs (type definitions).

In our ‘Todo’ application, we will create our typeDefs.js file in which we'll define a Todo Type and the corresponding queries and mutations.

const typeDefs = `#graphql
type Todo {
id: Int!
title: String!
done: Boolean!
createdAt: String!
}
type Query {
todos: [Todo!]!
}
type Mutation {
createTodo(title: String!): Todo!
updateTodo(id: Int!, done: Boolean!): Todo!
}
`;

In the above code:

  • We define a ‘Todo’ type, which includes fields of different types including ‘Int!’, ‘String!’, and ‘Boolean!’. The ‘!’ signifies that the field is non-nullable, meaning it cannot be null.
  • Next, we create a ‘Query’ type which acts as a read-only fetch operation and defines ways we can fetch data from our API. We define two queries: one to retrieve all todos (todos).
  • Lastly, we create a ‘Mutation’ type that describes operations that can change data on the server. We add three mutations: one for creating a new todo (createTodo), and one for updating an existing todo (updateTodo).

Resolvers

In GraphQL, a resolver is a function that retrieves data for a particular field in the schema from various data sources such as databases, APIs, and even other services. It is responsible for shaping and transforming the data in the response, returning a value that adheres to the type definitions and requirements specified in the GraphQL schema.

The createTodo resolver is a function that provides the instructions for turning a GraphQL operation (in this case, a mutation) into the data result. In our Todo application, it's used to instruct GraphQL on how to create a new todo record. Let's break down the createTodo resolver:

const resolvers = {
Mutation: {
createTodo: async (parent: never, args: { title: string }) => {
const newTodo = prisma.todo.create({
data: {
title: args.title,
done: false,
},
});
return newTodo;
},
}
}

In the above code, createTodo is an asynchronous function that takes in three arguments: parent, args, and context.

  • parent is an argument that is rarely used, but it contains the result of the parent field. Since createTodo is a root field, the parent argument doesn't apply in this case.
  • args is an object that holds all GraphQL arguments provided in the mutation. We're specifically using args.titleto get access to the title submitted in the mutation.

The create method is called on prisma.todo to create a new todo record in the database. The data object represents the values of the record, which include a title that is received from the mutation argument, and done flag which is set as false by default.

Lastly, the function returns the newly created todo record. The returned object should conform to the shape of the Todotype.

import prisma from "@/app/lib/prisma";
const resolvers = {
Mutation: {
createTodo: async (_: never, args: { title: string }) => {
const newTodo = prisma.todo.create({
data: {
title: args.title,
done: false,
},
});
return newTodo;
},
},
}

Lastly, the function returns the newly created todo record. The returned object should conform to the shape of the Todotype.

The updateTodo mutation is responsible for updating a specific existing todo record in our application. Take a look at the function for this resolver:

const resolvers = {
Mutation: {
...
updateTodo: async (_: never, args: { done: boolean; id: number }) => {
const updatedTodo = await prisma.todo.update({
where: {
id: args.id,
},
data: {
done: args.done,
},
});
return updatedTodo;
},
},
}

In this mutation, we take in two arguments from args: id and done.

  • args.id is used in the where clause to specify which todo record should be updated.
  • args.done contains our updated value for the done field.

Using Prisma's update method, we can easily specify which todo to update using the where clause, and what data to update using the data clause. We then return the updated todo, adhering to the Todo type structure.

The todos query is utilized to fetch all existing todos from our database. Here's how the resolver function for this query looks:

const resolvers = {
...
Query: {
todos: async () => prisma.todo.findMany(),
},
}
  • prisma.todo.findMany() is the Prisma Client query used to fetch all todos. findMany is a method provided by Prisma Client that lets you read records from your database.
  • The function then returns the array of todos. As specified in our type definitions, todos query should return an array of Todo.

This resolver is mapped to the todos field of our root Query type and executed whenever a todos query is made to our GraphQL API.

Now, you can restart your server and navigate to apollo (http://localhost:3000/api/graphql) where we can create a new todo :

You can test ours two others queries/mutations to check if our create mutate is correctly setup :

You can find the corresponding source code with tag “Part2.2” here.

--

--