Full Stack Personal Blog 06: GraphQL API Design
cz2025.06.03 14:41

GraphQL is a query language for APIs and a runtime for executing those queries. Unlike traditional REST APIs, GraphQL allows clients to request exactly the data they need—nothing more, nothing less—optimizing the data fetching process. All requests are handled through a single endpoint, and clients can combine multiple resource queries into a single request, eliminating the need for multiple HTTP calls. GraphQL's flexibility and efficiency make it ideal for dynamic data structures and complex applications, especially when efficient data transfer and cross-platform interaction are required.

1. GraphQL Schema Definition and Resolvers

  1. Define the schema

The schema defines the types of fields and the structure of the data. When you make an invalid operation, the GraphQL engine will tell you exactly where the problem is, with detailed error messages, making development and debugging very friendly.

GraphQL has built-in types like String, Int, Float, Boolean, and ID. You can also define your own types.

type User {
  id: ID
  email: String
  image: String
  role: Role
  posts: [Post]
}

enum Role {
  ADMIN
  USER
}

type Post {
  id: ID
  category: String
  description: String
  title: String
  likes: [User]
}

From the schema, you can see that an author can have multiple posts, and a post can have multiple users who like it.

  1. Define data queries

To query all posts, use ! to indicate non-null. The syntax is almost the same as TypeScript.

// graphql/schema.ts
export const typeDefs = `
  type Query {
    posts: [Post]!
  }
`

You can also query a specific post by its id:

// graphql/schema.ts
export const typeDefs = `
  type Query {
    post(id: ID!): Post!
  }
`
  1. Define data mutations

Define mutations for creating, deleting, and updating posts:

// graphql/schema.ts
export const typeDefs = `
  ...

  type Mutation {
    createPost(category: String!, description: String!,  title: String!): Post!
    deletePost(id: ID!): Post!
    updatePost(category: String, description: String, id: String, title: String): Post!
  }
`
  1. Define resolver functions

When the frontend sends a request to the backend, the corresponding resolver function provides the response data. For example, to query all posts, you can use Prisma to query the database:

// /graphql/resolvers.ts
import prisma from '../lib/prisma'
export const resolvers = {
  Query: {
    posts: () => {
      return prisma.post.findMany()
    },
  },
}

3. Create a GraphQL Service

  1. Install dependencies

pnpm add graphql graphql-yoga

graphql-yoga is a popular GraphQL server framework with a built-in GraphQL Playground, making it convenient for developers to test and debug APIs. It's great for rapid development and learning.

  1. Create the service endpoint
// src/api/graphql/route.ts

import { createSchema, createYoga } from 'graphql-yoga'
import { resolvers } from '../../graphql/resolvers'
import { typeDefs } from '../../graphql/schema'
import { NextRequest } from 'next/server'

const { handleRequest } = createYoga({
  schema: createSchema({
    typeDefs,
    resolvers,
  }),
})

export async function GET(request: NextRequest) {
  return handleRequest(request, {} as any)
}

export async function POST(request: NextRequest) {
  return handleRequest(request, {} as any)
}

Now, run pnpm dev and visit http://localhost:3000/api/graphql/ to see the GraphiQL Playground. Enter the following code to query the list of posts, specifying the id and title fields from the Post model:

query {
  posts {
    id
    title
  }
}

Comments