본문으로 건너뛰기 GraphQL Complete Guide | Schema· Resolver

GraphQL Complete Guide | Schema· Resolver

GraphQL Complete Guide | Schema· Resolver

이 글의 핵심

A practical guide to building efficient APIs with GraphQL—defining schemas, resolvers, queries, mutations, and subscriptions, plus Apollo Server and Apollo Client examples in TypeScript.

What this post is about

This is a complete guide to building efficient APIs with GraphQL. It covers schema definition, resolvers, queries, mutations, subscriptions, and Apollo Server/Client—with runnable examples.

From experience: Migrating a REST API to GraphQL cut API calls by about 70% and roughly doubled perceived performance in our mobile app by shipping exactly the fields each screen needed.

Introduction: “Our REST API feels wasteful”

Real-world pain points

Scenario 1: Over-fetching

You receive fields you never use. GraphQL lets the client request only what it needs.

Scenario 2: Under-fetching

You chain several REST calls to assemble a screen. GraphQL can fetch the graph in one request.

Scenario 3: Version sprawl

You maintain /v1, /v2, and so on. GraphQL typically evolves the schema without URL versions—clients opt into new fields.


1. What is GraphQL?

Core traits

GraphQL is a query language for APIs.

Main benefits:

  • Precise data: ask for exactly what you need
  • Single endpoint: often one /graphql URL
  • Type system: strong contracts between client and server
  • Real time: subscriptions for push-style updates
  • Self-describing: the schema doubles as documentation

REST vs GraphQL:

  • REST: e.g. three calls (user, posts, comments)
  • GraphQL: one call for a composed query

2. Apollo Server

Install

npm install @apollo/server graphql

Minimal server

// server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
  }
  type Query {
    users: [User!]!
    user(id: ID!): User
  }
`;
const users = [
  { id: '1', name: 'John', email: 'john@example.com' },
  { id: '2', name: 'Jane', email: 'jane@example.com' },
];
const resolvers = {
  Query: {
    users: () => users,
    user: (_: any, { id }: { id: string }) => 
      users.find(u => u.id === id),
  },
};
const server = new ApolloServer({
  typeDefs,
  resolvers,
});
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});
console.log(`Server ready at ${url}`);

3. Defining the schema

Types

type User {
  id: ID!
  name: String!
  email: String!
  age: Int
  posts: [Post!]!
}
type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  createdAt: String!
}
type Comment {
  id: ID!
  text: String!
  author: User!
  post: Post!
}

Query

type Query {
  users: [User!]!
  user(id: ID!): User
  posts(limit: Int, offset: Int): [Post!]!
  post(id: ID!): Post
}

Mutation

type Mutation {
  createUser(name: String!, email: String!): User!
  updateUser(id: ID!, name: String, email: String): User!
  deleteUser(id: ID!): Boolean!
  
  createPost(title: String!, content: String!, authorId: ID!): Post!
}

Subscription

type Subscription {
  postCreated: Post!
  commentAdded(postId: ID!): Comment!
}

4. Resolvers

Basic resolvers

const resolvers = {
  Query: {
    users: async () => {
      return await db.user.findMany();
    },
    
    user: async (_: any, { id }: { id: string }) => {
      return await db.user.findUnique({ where: { id: parseInt(id) } });
    },
  },
  Mutation: {
    createUser: async (_: any, { name, email }: { name: string; email: string }) => {
      return await db.user.create({
        data: { name, email },
      });
    },
  },
  User: {
    posts: async (parent: any) => {
      return await db.post.findMany({
        where: { authorId: parent.id },
      });
    },
  },
};

5. Apollo Client (React)

Install

npm install @apollo/client graphql

Setup

// src/lib/apollo.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
export const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache(),
});
// src/App.tsx
import { ApolloProvider } from '@apollo/client';
import { client } from './lib/apollo';
function App() {
  return (
    <ApolloProvider client={client}>
      <YourApp />
    </ApolloProvider>
  );
}

6. Using queries

useQuery

import { gql, useQuery } from '@apollo/client';
const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;
function UsersList() {
  const { loading, error, data } = useQuery(GET_USERS);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>
          {user.name} ({user.email})
        </li>
      ))}
    </ul>
  );
}

Variables

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
      posts {
        id
        title
      }
    }
  }
`;
function UserProfile({ userId }: { userId: string }) {
  const { data } = useQuery(GET_USER, {
    variables: { id: userId },
  });
  return (
    <div>
      <h1>{data?.user.name}</h1>
      <p>{data?.user.email}</p>
      <h2>Posts</h2>
      <ul>
        {data?.user.posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

7. Using mutations

useMutation

import { gql, useMutation } from '@apollo/client';
const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;
function CreateUserForm() {
  const [createUser, { loading, error }] = useMutation(CREATE_USER, {
    refetchQueries: [{ query: GET_USERS }],
  });
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    await createUser({
      variables: {
        name: formData.get('name'),
        email: formData.get('email'),
      },
    });
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create User'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

8. Subscriptions

Server

import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
const schema = makeExecutableSchema({ typeDefs, resolvers });
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
});
useServer({ schema }, wsServer);

Client

import { useSubscription, gql } from '@apollo/client';
const POST_CREATED = gql`
  subscription OnPostCreated {
    postCreated {
      id
      title
      author {
        name
      }
    }
  }
`;
function PostFeed() {
  const { data, loading } = useSubscription(POST_CREATED);
  if (loading) return <p>Waiting for posts...</p>;
  return (
    <div>
      <p>New post: {data?.postCreated.title}</p>
    </div>
  );
}

Tying this to interviews and your resume

N+1 issues, schema design, and REST vs GraphQL trade-offs show up often in API and backend interviews. For structured prep, see the Technical Interview Preparation Guide. To turn project experience into resume-ready outcomes, use the project and impact sections in Developer Job Hunting: Practical Tips.


Summary and checklist

Key takeaways

  • GraphQL: a query language for APIs
  • Precise data: request only the fields you need
  • Single endpoint: typically /graphql
  • Type system: strong contracts and tooling
  • Real time: subscriptions when you need live updates
  • Apollo: the most widely used JS/TS stack for GraphQL

Implementation checklist

  • Set up Apollo Server
  • Define the schema
  • Implement resolvers
  • Configure Apollo Client
  • Implement queries and mutations
  • Add subscriptions (optional)
  • Deploy


Keywords covered in this post

GraphQL, API, Apollo, Schema, Resolver, Backend, TypeScript

Frequently asked questions (FAQ)

Q. GraphQL vs REST—which should I choose?

A. GraphQL fits complex, client-driven data needs. REST stays simpler and is often easier to cache with standard HTTP tooling. For mobile apps with varied UIs, GraphQL is a strong fit; for straightforward APIs, REST may be enough.

Q. How do I solve the N+1 problem?

A. Use DataLoader for per-request batching and caching of related loads.

Q. How should I approach caching?

A. Apollo Client caches query results on the client. For server-side caching, add Redis (or similar) where resolver work is expensive.

Q. Is GraphQL safe for production?

A. Yes. Many large companies run it in production—plan for performance, limits, and observability like any critical API layer.


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. A practical guide to building efficient APIs with GraphQL—defining schemas, resolvers, queries, mutations, and subscriptions, plus Apollo Server and Apollo Client examples in TypeScript.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers GraphQL, Web-API, Apollo, Schema, Resolver, Backend, TypeScript.