본문으로 건너뛰기

Cursor에서의 GraphQL API 개발

Cursor에서의 GraphQL API 개발

GraphQL은 클라이언트가 필요한 데이터를 정확히 요청할 수 있게 함으로써 API 구축 방식을 혁신했습니다. AI 기반 IDE 편집기인 Cursor와 결합하면 GraphQL 개발 워크플로우가 훨씬 더 빠르고 지능적으로 변합니다. 이 가이드는 Cursor의 고급 기능을 사용하여 스키마 설계부터 프로덕션 준비 리졸버 구현까지 모든 것을 다룹니다.

Cursor에서의 GraphQL 소개​

Cursor는 지능형 코드 완성, AI 기반 코드 생성, 스키마 관계에 대한 깊은 이해를 통해 GraphQL 개발을 위한 탁월한 환경을 제공합니다. 새로운 API를 처음부터 구축하든 기존 API를 유지 관리하든, Cursor의 기능은 더 적은 노력으로 더 나은 GraphQL 코드를 작성하는 데 도움을 줍니다.

Cursor와 GraphQL 도구의 통합을 통해 다음을 수행할 수 있습니다:

  • TypeScript 타입에서 스키마를 자동으로 생성
  • 리졸버 구현에 대한 지능형 제안 수신
  • 스키마에 대한 쿼리를 실시간으로 검증
  • AI 지원으로 대규모 스키마 리팩토링
  • 코드에서 직접 문서 생성

구현 세부 사항에 들어가기 전에 작업 공간에 다음 도구가 설치되어 있는지 확인하세요:

  • Node.js 18 이상
  • Cursor IDE (최신 버전 권장)
  • 쿼리 테스트를 위한 GraphQL 클라이언트 확장

GraphQL 프로젝트 설정​

잘 구조화된 프로젝트는 확장 가능한 GraphQL 개발에 필수적입니다. Cursor는 처음부터 코드베이스를 구성하는 데 탁월한 도움을 줍니다.

새 프로젝트 디렉토리를 만들고 선호하는 패키지 관리자로 초기화하는 것으로 시작하세요. 이 디렉토리에서 Cursor를 열고 다음 폴더 구조를 만드세요:

src/
schema/
typeDefs.ts
resolvers/
index.ts
userResolver.ts
postResolver.ts
prisma/
schema.prisma
server.ts

이 구조는 관심사를 명확히 분리하고 Cursor의 AI가 스키마 정의, 리졸버 로직, 데이터베이스 모델 간의 경계를 이해할 수 있게 합니다. 이 구조를 Cursor에서 열면 편집기가 자동으로 모든 타입 관계를 인덱싱하고 파일 간 제안을 제공합니다.

Apollo Server 및 Prisma 설정을 위한 핵심 종속성을 설치하세요:

npm install @apollo/server graphql
npm install @prisma/client
npm install -D prisma typescript ts-node @types/node

설치 후 프로젝트 내에서 Prisma를 초기화하세요. Cursor의 터미널 통합은 이를 원활하게 만들고, AI는 설정 중 발생하는 구성 문제를 디버깅하는 데 도움을 줄 수 있습니다.

팁

Cursor의 인라인 채팅을 사용하여 패키지 호환성에 대한 질문을 하세요. 예를 들어 package.json을 선택하고 "이 GraphQL 종속성이 최신 Apollo Server 버전과 호환되나요?"라고 물어보세요. Cursor가 버전을 분석하고 업데이트를 제안합니다.

AI 지원으로 스키마 설계​

스키마 설계는 모든 GraphQL API의 기초입니다. Cursor의 AI 기능은 애플리케이션 요구 사항에 따라 타입, 필드, 관계를 제안하여 이 프로세스를 더 직관적으로 만듭니다.

타입 정의 생성​

src/schema/typeDefs.ts에서 핵심 타입을 정의하는 것으로 시작하세요. Cursor는 내장 스칼라, 지시어, 사용자 정의 타입을 포함한 GraphQL SDL(스키마 정의 언어) 구문에 대한 지능형 자동 완성을 제공합니다.

export const typeDefs = `#graphql
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
createdAt: String!
updatedAt: String!
}

type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
createdAt: String!
updatedAt: String!
}

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

type Mutation {
createUser(email: String!, name: String): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
publishPost(id: ID!): Post!
deletePost(id: ID!): Post!
}
`;

Cursor에서 타입 정의를 입력할 때 편집기는 GraphQL 구문을 인식하고 컨텍스트 인식 제안을 제공합니다. AI는 타입 간의 관계를 이해하고 새로운 엔티티를 정의할 때 누락된 필드를 제안할 수 있습니다.

Prisma 모델에서 스키마 생성​

Cursor에서 가장 강력한 워크플로우 중 하나는 Prisma 모델에서 직접 GraphQL 스키마를 생성하는 것입니다. prisma/schema.prisma에서 데이터베이스 스키마를 정의한 후 Cursor의 AI 채팅을 사용하여 이러한 모델을 GraphQL 타입 정의로 변환할 수 있습니다.

다음은 Prisma 스키마 예제입니다:

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @default(uuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Post {
id String @id @default(uuid())
title String
content String
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Cursor에서 Prisma 스키마를 선택하고 "이 Prisma 모델에 대한 해당 GraphQL 타입 정의를 생성하세요. Query 및 Mutation 타입을 포함하세요."와 같은 프롬프트로 AI 채팅 기능을 사용하세요. Cursor는 데이터베이스 구조를 반영하는 정확한 SDL을 생성합니다.

경고

데이터베이스 모델에 캡처되지 않을 수 있는 비즈니스 로직 요구 사항에 대해 AI 생성 스키마를 항상 검토하세요. AI는 구조를 이해하지만 애플리케이션에 필요한 도메인별 유효성 검사 규칙이나 사용자 정의 스칼라를 놓칠 수 있습니다.

Cursor에서 리졸버 구현​

리졸버는 GraphQL API가 생명을 얻는 곳입니다. Cursor의 TypeScript 통합은 타입 안전 리졸버 작성을 훨씬 더 생산적으로 만듭니다.

타입 안전 리졸버 작성​

완전한 TypeScript 지원으로 리졸버 파일을 만드세요. Cursor는 스키마와 리졸버 타입 간의 관계를 이해하여 부모 객체, 인수, 컨텍스트에 대한 자동 완성을 제공합니다.

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const userResolver = {
Query: {
users: async () => {
return await prisma.user.findMany({
include: { posts: true },
});
},

user: async (_: unknown, { id }: { id: string }) => {
return await prisma.user.findUnique({
where: { id },
include: { posts: true },
});
},
},

User: {
posts: async (parent: { id: string }) => {
return await prisma.post.findMany({
where: { authorId: parent.id },
});
},
},

Mutation: {
createUser: async (
_: unknown,
{ email, name }: { email: string; name?: string }
) => {
return await prisma.user.create({
data: { email, name },
});
},
},
};

Cursor의 IntelliSense는 스키마 정의를 기반으로 리졸버 인수에 대한 정확한 타입 정보를 제공합니다. 매개변수나 반환 값 위에 마우스를 올리면 TypeScript에 매핑된 정확한 GraphQL 타입을 볼 수 있습니다.

AI를 사용한 복잡한 리졸버 생성​

데이터 변환, 집계 또는 타사 API 호출과 관련된 복잡한 리졸버 로직의 경우 Cursor의 AI 생성이 매우 유용합니다. 스키마의 일부를 선택하고 Cursor에게 해당 리졸버 구현을 생성하도록 요청하세요.

예를 들어, 다음과 같은 고급 스키마 정의가 주어졌을 때:

type Query {
searchPosts(query: String!, filters: PostFilters): SearchResult!
}

input PostFilters {
publishedOnly: Boolean
dateFrom: String
dateTo: String
authorIds: [ID!]
}

type SearchResult {
posts: [Post!]!
totalCount: Int!
hasMore: Boolean!
}

Cursor에게 다음과 같이 프롬프트할 수 있습니다: "제공된 기준으로 필터링하고, 기본 제한 20으로 페이지네이션을 지원하며, 총 개수와 hasMore 플래그를 반환하는 searchPosts 리졸버를 구현하세요." AI는 Prisma의 필터링 API를 사용하여 완전한 구현을 생성합니다.

searchPosts: async (
_: unknown,
{
query,
filters,
}: {
query: string;
filters?: {
publishedOnly?: boolean;
dateFrom?: string;
dateTo?: string;
authorIds?: string[];
};
},
context: { userId?: string }
) => {
const limit = 20;
const where: any = {
OR: [
{ title: { contains: query, mode: 'insensitive' } },
{ content: { contains: query, mode: 'insensitive' } },
],
};

if (filters?.publishedOnly) {
where.published = true;
}

if (filters?.dateFrom || filters?.dateTo) {
where.createdAt = {};
if (filters.dateFrom) where.createdAt.gte = new Date(filters.dateFrom);
if (filters.dateTo) where.createdAt.lte = new Date(filters.dateTo);
}

if (filters?.authorIds?.length) {
where.authorId = { in: filters.authorIds };
}

const [posts, totalCount] = await Promise.all([
prisma.post.findMany({
where,
take: limit,
include: { author: true },
}),
prisma.post.count({ where }),
]);

return {
posts,
totalCount,
hasMore: totalCount > limit,
};
},
팁

복잡한 리졸버를 생성할 때는 항상 Prisma 쿼리에서 N+1 문제를 확인하세요. Cursor의 AI는 종종 관련 엔티티의 배치 로딩을 위한 DataLoader 패턴을 포함하지 않을 수 있는 기본 구현을 생성합니다.

Apollo Server 통합​

스키마와 리졸버를 Apollo Server에 연결하는 것은 Cursor의 지원으로 간단합니다. 편집기는 올바른 TypeScript 타입과 미들웨어로 서버를 구성하는 데 도움을 줍니다.

서버 설정​

Apollo Server 4 구성으로 server.ts 파일을 만드세요:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './schema/resolvers';

interface MyContext {
token?: string;
userId?: string;
}

const server = new ApolloServer<MyContext>({
typeDefs,
resolvers,
});

const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }): Promise<MyContext> => {
const token = req.headers.authorization || '';
// 여기에 인증 로직 추가
return { token };
},
});

console.log(`Server ready at: ${url}`);

Cursor는 Apollo Server API에 대한 자동 완성을 제공하고 구성 전반에 걸쳐 올바른 타입 지정을 유지하는 데 도움을 줍니다. AI는 또한 인증, 로깅, 오류 처리를 위한 미들웨어 패턴을 제안할 수 있습니다.

사용자 정의 스칼라 및 지시어 추가​

고급 GraphQL API는 종종 특수 데이터 타입을 위한 사용자 정의 스칼라가 필요합니다. Cursor는 완전한 타입 안전성을 갖춘 구현을 도와줍니다.

import { GraphQLScalarType, Kind } from 'graphql';

export const dateScalar = new GraphQLScalarType({
name: 'Date',
description: 'Custom Date scalar',
serialize(value: unknown) {
if (value instanceof Date) {
return value.toISOString();
}
throw Error('GraphQL Date Scalar serializer expected a Date object');
},
parseValue(value: unknown) {
if (typeof value === 'string') {
return new Date(value);
}
throw new Error('GraphQL Date Scalar parser expected a string');
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
},
});

사용자 정의 스칼라를 정의할 때 Cursor의 TypeScript 언어 서버는 직렬화 및 구문 분석 함수가 올바른 타입을 처리하도록 보장하여 런타임 오류를 방지합니다.

Prisma 통합 모범 사례​

Cursor에서 Prisma와 GraphQL의 조합은 강력한 풀스택 개발 경험을 만듭니다. 이 섹션에서는 이러한 기술을 효과적으로 통합하기 위한 모범 사례를 다룹니다.

Prisma 클라이언트 타입 생성​

Prisma 스키마를 수정한 후 generate 명령을 실행하세요. Cursor의 터미널 통합은 출력을 캡처하고 오류가 있는 경우 AI가 스키마 구문 문제를 해결하는 데 도움을 줄 수 있습니다.

npx prisma generate

생성된 Prisma 클라이언트 타입은 Cursor의 IntelliSense와 원활하게 통합됩니다. 리졸버를 작성할 때 모델 필드, 필터 옵션, 관계 쿼리에 대한 완전한 자동 완성을 얻을 수 있습니다.

DataLoader를 사용한 관계 처리​

GraphQL 리졸버에서 N+1 쿼리 문제를 방지하려면 Cursor 프로젝트에 DataLoader를 통합하세요. AI는 Prisma가 효율적으로 실행할 배치 로딩 함수를 설정하는 데 도움을 줄 수 있습니다.

import DataLoader from 'dataloader';
import { PrismaClient, User } from '@prisma/client';

const prisma = new PrismaClient();

export const createUserLoader = () => {
return new DataLoader<string, User | null>(async (userIds) => {
const users = await prisma.user.findMany({
where: { id: { in: [...userIds] } },
});

const userMap = new Map(users.map((user) => [user.id, user]));
return userIds.map((id) => userMap.get(id) || null);
});
};

서버 컨텍스트에서 로더를 초기화하고 리졸버에 전달하세요:

interface MyContext {
userLoader: ReturnType<typeof createUserLoader>;
postLoader: ReturnType<typeof createPostLoader>;
}

const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async (): Promise<MyContext> => {
return {
userLoader: createUserLoader(),
postLoader: createPostLoader(),
};
},
});
경고

DataLoader 인스턴스는 다른 사용자 간의 캐싱 문제를 방지하기 위해 요청별로 생성되어야 합니다. Cursor의 AI는 때때로 로더에 대한 싱글톤 패턴을 제안합니다. 구현이 각 GraphQL 요청에 대해 새 인스턴스를 생성하는지 항상 확인하세요.

AI 쿼리 생성 및 테스트​

Cursor의 뛰어난 기능 중 하나는 스키마를 기반으로 GraphQL 쿼리를 생성하고 테스트하는 능력입니다. 이는 개발 및 디버깅 워크플로우를 모두 가속화합니다.

스키마에서 쿼리 생성​

Cursor에서 타입 정의를 선택하고 AI에게 샘플 쿼리와 뮤테이션을 생성하도록 요청하세요. 이는 사용 가능한 API 표면을 이해해야 하는 프론트엔드 개발자에게 특히 유용합니다.

위의 User 및 Post 스키마에 대해 Cursor는 다음과 같이 생성할 수 있습니다:

query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
id
email
name
posts {
id
title
published
createdAt
}
}
}

mutation CreateNewPost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
title
author {
id
name
}
}
}

이러한 생성된 쿼리는 완전히 타입이 지정되고 스키마에 대해 검증되어 프론트엔드 통합 오류의 가능성을 줄입니다.

생성된 쿼리로 리졸버 테스트​

Cursor의 AI를 사용하여 리졸버에 대한 포괄적인 테스트 스위트를 생성하세요. 리졸버 함수를 선택하고 "이 리졸버에 대한 Jest 테스트를 생성하세요. 모의 Prisma 호출과 엣지 케이스를 포함하세요."라고 프롬프트하세요.

import { userResolver } from './userResolver';
import { PrismaClient } from '@prisma/client';

jest.mock('@prisma/client', () => {
const mockPrisma = {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
},
};
return { PrismaClient: jest.fn(() => mockPrisma) };
});

describe('User Resolver', () => {
const prisma = new PrismaClient();

beforeEach(() => {
jest.clearAllMocks();
});

it('should return all users with their posts', async () => {
const mockUsers = [
{ id: '1', email: '[email protected]', posts: [] },
{ id: '2', email: '[email protected]', posts: [] },
];
(prisma.user.findMany as jest.Mock).mockResolvedValue(mockUsers);

const result = await userResolver.Query.users();

expect(prisma.user.findMany).toHaveBeenCalledWith({
include: { posts: true },
});
expect(result).toEqual(mockUsers);
});

it('should return a single user by id', async () => {
const mockUser = { id: '1', email: '[email protected]', posts: [] };
(prisma.user.findUnique as jest.Mock).mockResolvedValue(mockUser);

const result = await userResolver.Query.user(null, { id: '1' });

expect(prisma.user.findUnique).toHaveBeenCalledWith({
where: { id: '1' },
include: { posts: true },
});
expect(result).toEqual(mockUser);
});
});

오류 처리 및 유효성 검사​

견고한 GraphQL API는 포괄적인 오류 처리가 필요합니다. Cursor는 리졸버 전반에 걸쳐 일관된 오류 패턴을 구현하는 데 도움을 줍니다.

사용자 정의 오류 클래스​

Apollo의 GraphQL 오류 타입을 확장하는 사용자 정의 오류 클래스를 정의하세요:

import { GraphQLError } from 'graphql';

export class AuthenticationError extends GraphQLError {
constructor(message: string = 'Not authenticated') {
super(message, {
extensions: { code: 'UNAUTHENTICATED', http: { status: 401 } },
});
}
}

export class ValidationError extends GraphQLError {
constructor(message: string) {
super(message, {
extensions: { code: 'VALIDATION_FAILED', http: { status: 400 } },
});
}
}

export class NotFoundError extends GraphQLError {
constructor(resource: string) {
super(`${resource} not found`, {
extensions: { code: 'NOT_FOUND', http: { status: 404 } },
});
}
}

Cursor의 자동 완성 지원으로 이러한 오류를 리졸버에서 사용하세요:

user: async (_: unknown, { id }: { id: string }) => {
const user = await prisma.user.findUnique({
where: { id },
include: { posts: true },
});

if (!user) {
throw new NotFoundError('User');
}

return user;
},
팁

오류를 로깅하고 클라이언트를 위해 포맷하는 Apollo Server용 오류 플러그인을 만드세요. Cursor는 사용자 정의 오류 클래스를 기반으로 이 플러그인을 생성할 수 있습니다.

결론​

Cursor에서 GraphQL API를 개발하는 것은 현대적인 IDE의 강력함과 지능형 AI 지원을 결합합니다. 스키마 설계부터 리졸버 구현, 테스트, 배포에 이르기까지 Cursor는 GraphQL 개발의 모든 측면을 간소화합니다.

이 가이드의 주요 내용:

  • Cursor의 AI를 사용하여 Prisma 모델과 TypeScript 타입에서 스키마 생성
  • 타입 안전 리졸버 개발을 위한 TypeScript 통합 활용
  • N+1 쿼리를 방지하기 위한 DataLoader 패턴 구현
  • 스키마 정의에서 테스트 및 쿼리 자동 생성
  • 사용자 정의 오류 클래스로 일관된 오류 처리 유지

GraphQL 및 Cursor로 계속 개발하면서 대규모 스키마 리팩토링, 데이터베이스 쿼리 최적화, 문서 생성을 위해 AI 채팅 기능을 실험해 보세요. 인간의 전문성과 AI 지원의 조합은 견고하고 성능이 뛰어나며 유지 관리가 가능한 API를 생산합니다.