Best practices for designing scalable GraphQL schemas with type safety and efficient resolvers for Google Antigravity.
# GraphQL Schema SDL Patterns for Google Antigravity
GraphQL provides a powerful query language for APIs with strong typing and flexible data fetching. Google Antigravity's Gemini 3 helps you design well-structured schemas that scale with your application's needs.
## Schema-First Design
Start with a well-organized schema using SDL (Schema Definition Language):
```graphql
# schema.graphql
scalar DateTime
scalar UUID
scalar EmailAddress
enum UserRole {
USER
ADMIN
MODERATOR
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
input CreateUserInput {
email: EmailAddress!
name: String!
role: UserRole
}
input UpdateUserInput {
name: String
bio: String
avatarUrl: String
}
input CreatePostInput {
title: String!
content: String!
categoryIds: [UUID!]
tags: [String!]
}
interface Node {
id: UUID!
}
interface Timestamped {
createdAt: DateTime!
updatedAt: DateTime!
}
type User implements Node & Timestamped {
id: UUID!
email: EmailAddress!
name: String!
role: UserRole!
bio: String
avatarUrl: String
posts(first: Int, after: String): PostConnection!
createdAt: DateTime!
updatedAt: DateTime!
}
type Post implements Node & Timestamped {
id: UUID!
title: String!
content: String!
status: PostStatus!
author: User!
categories: [Category!]!
tags: [String!]!
viewCount: Int!
createdAt: DateTime!
updatedAt: DateTime!
}
type Category implements Node {
id: UUID!
name: String!
slug: String!
posts(first: Int, after: String): PostConnection!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type PostEdge {
cursor: String!
node: Post!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type Query {
me: User
user(id: UUID!): User
users(first: Int, after: String, role: UserRole): UserConnection!
post(id: UUID!): Post
posts(
first: Int
after: String
status: PostStatus
authorId: UUID
categoryId: UUID
): PostConnection!
categories: [Category!]!
search(query: String!, first: Int): SearchResultConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: UUID!, input: UpdateUserInput!): User!
deleteUser(id: UUID!): Boolean!
createPost(input: CreatePostInput!): Post!
updatePost(id: UUID!, input: UpdatePostInput!): Post!
publishPost(id: UUID!): Post!
deletePost(id: UUID!): Boolean!
}
type Subscription {
postPublished: Post!
newComment(postId: UUID!): Comment!
}
```
## TypeScript Code Generation
Use GraphQL Code Generator for type safety:
```typescript
// codegen.ts
import { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "./schema.graphql",
documents: ["src/**/*.graphql", "src/**/*.tsx"],
generates: {
"./src/generated/graphql.ts": {
plugins: [
"typescript",
"typescript-operations",
"typescript-react-apollo",
],
config: {
strictScalars: true,
scalars: {
DateTime: "string",
UUID: "string",
EmailAddress: "string",
},
withHooks: true,
withComponent: false,
withHOC: false,
},
},
},
};
export default config;
```
## Resolver Implementation
Implement type-safe resolvers with context:
```typescript
// resolvers/index.ts
import { Resolvers } from "@/generated/graphql";
import { Context } from "@/lib/context";
import { GraphQLError } from "graphql";
export const resolvers: Resolvers<Context> = {
Query: {
me: async (_, __, { user }) => {
if (!user) return null;
return user;
},
user: async (_, { id }, { prisma }) => {
return prisma.user.findUnique({
where: { id },
});
},
posts: async (_, { first = 10, after, status, authorId }, { prisma }) => {
const cursor = after ? { id: after } : undefined;
const where = {
...(status && { status }),
...(authorId && { authorId }),
};
const posts = await prisma.post.findMany({
take: first + 1,
skip: after ? 1 : 0,
cursor,
where,
orderBy: { createdAt: "desc" },
});
const hasNextPage = posts.length > first;
const edges = posts.slice(0, first).map((post) => ({
cursor: post.id,
node: post,
}));
return {
edges,
pageInfo: {
hasNextPage,
hasPreviousPage: !!after,
startCursor: edges[0]?.cursor,
endCursor: edges[edges.length - 1]?.cursor,
},
totalCount: await prisma.post.count({ where }),
};
},
},
Mutation: {
createPost: async (_, { input }, { user, prisma }) => {
if (!user) {
throw new GraphQLError("Not authenticated", {
extensions: { code: "UNAUTHENTICATED" },
});
}
return prisma.post.create({
data: {
title: input.title,
content: input.content,
authorId: user.id,
status: "DRAFT",
categories: {
connect: input.categoryIds?.map((id) => ({ id })) || [],
},
tags: input.tags || [],
},
});
},
publishPost: async (_, { id }, { user, prisma }) => {
const post = await prisma.post.findUnique({ where: { id } });
if (!post) {
throw new GraphQLError("Post not found", {
extensions: { code: "NOT_FOUND" },
});
}
if (post.authorId !== user?.id) {
throw new GraphQLError("Not authorized", {
extensions: { code: "FORBIDDEN" },
});
}
return prisma.post.update({
where: { id },
data: { status: "PUBLISHED" },
});
},
},
User: {
posts: async (parent, { first = 10, after }, { prisma }) => {
const posts = await prisma.post.findMany({
where: { authorId: parent.id },
take: first + 1,
skip: after ? 1 : 0,
cursor: after ? { id: after } : undefined,
orderBy: { createdAt: "desc" },
});
const hasNextPage = posts.length > first;
const edges = posts.slice(0, first).map((post) => ({
cursor: post.id,
node: post,
}));
return {
edges,
pageInfo: {
hasNextPage,
hasPreviousPage: !!after,
startCursor: edges[0]?.cursor,
endCursor: edges[edges.length - 1]?.cursor,
},
totalCount: await prisma.post.count({ where: { authorId: parent.id } }),
};
},
},
Post: {
author: async (parent, _, { dataloaders }) => {
return dataloaders.userLoader.load(parent.authorId);
},
categories: async (parent, _, { prisma }) => {
return prisma.category.findMany({
where: {
posts: { some: { id: parent.id } },
},
});
},
},
};
```
## DataLoader for N+1 Prevention
Implement efficient data loading:
```typescript
// lib/dataloaders.ts
import DataLoader from "dataloader";
import { PrismaClient, User, Category } from "@prisma/client";
export function createDataLoaders(prisma: PrismaClient) {
return {
userLoader: new DataLoader<string, User>(async (ids) => {
const users = await prisma.user.findMany({
where: { id: { in: [...ids] } },
});
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id)!);
}),
categoryLoader: new DataLoader<string, Category>(async (ids) => {
const categories = await prisma.category.findMany({
where: { id: { in: [...ids] } },
});
const categoryMap = new Map(categories.map((c) => [c.id, c]));
return ids.map((id) => categoryMap.get(id)!);
}),
};
}
```
## Best Practices
1. **Use interfaces for shared fields** - Promote schema consistency
2. **Implement cursor-based pagination** - Scale better than offset pagination
3. **Use DataLoader** - Prevent N+1 query problems
4. **Define clear input types** - Separate mutation inputs from query types
5. **Add proper error codes** - Help clients handle errors appropriately
GraphQL with Google Antigravity enables rapid API development with intelligent schema suggestions and type-safe resolver generation.This graphql prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their graphql implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For graphql projects, consider mentioning your framework version, coding style, and any specific libraries you're using.