Production-ready GraphQL APIs with resolvers, dataloaders, and schema design
# GraphQL API Patterns for Google Antigravity
Build efficient GraphQL APIs using Google Antigravity's Gemini 3 engine. This guide covers schema design, resolvers, dataloaders, and authentication patterns.
## Schema Definition
```typescript
// src/schema/typeDefs.ts
import { gql } from 'graphql-tag';
export const typeDefs = gql`
scalar DateTime
scalar JSON
type Query {
# Users
me: User
user(id: ID!): User
users(input: UsersInput): UsersConnection!
# Products
product(id: ID!): Product
products(input: ProductsInput): ProductsConnection!
# Orders
order(id: ID!): Order
orders(input: OrdersInput): OrdersConnection!
}
type Mutation {
# Auth
login(input: LoginInput!): AuthPayload!
register(input: RegisterInput!): AuthPayload!
refreshToken(token: String!): AuthPayload!
# Users
updateProfile(input: UpdateProfileInput!): User!
updatePassword(input: UpdatePasswordInput!): Boolean!
# Products
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product!
deleteProduct(id: ID!): Boolean!
# Orders
createOrder(input: CreateOrderInput!): Order!
updateOrderStatus(id: ID!, status: OrderStatus!): Order!
cancelOrder(id: ID!): Order!
}
type Subscription {
orderStatusChanged(orderId: ID!): Order!
newNotification: Notification!
}
# User types
type User {
id: ID!
email: String!
name: String!
role: UserRole!
avatar: String
orders(first: Int, after: String): OrdersConnection!
createdAt: DateTime!
updatedAt: DateTime!
}
enum UserRole {
ADMIN
USER
}
input UsersInput {
first: Int
after: String
search: String
role: UserRole
}
type UsersConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
# Product types
type Product {
id: ID!
name: String!
slug: String!
description: String
price: Float!
stock: Int!
category: Category
images: [String!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Category {
id: ID!
name: String!
slug: String!
products(first: Int, after: String): ProductsConnection!
}
input ProductsInput {
first: Int
after: String
search: String
categoryId: ID
minPrice: Float
maxPrice: Float
inStock: Boolean
}
type ProductsConnection {
edges: [ProductEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ProductEdge {
node: Product!
cursor: String!
}
# Order types
type Order {
id: ID!
user: User!
items: [OrderItem!]!
status: OrderStatus!
totalAmount: Float!
shippingAddress: Address!
createdAt: DateTime!
updatedAt: DateTime!
}
type OrderItem {
id: ID!
product: Product!
quantity: Int!
price: Float!
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
type Address {
street: String!
city: String!
state: String!
zipCode: String!
country: String!
}
# Pagination
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Auth
type AuthPayload {
user: User!
accessToken: String!
refreshToken: String!
}
input LoginInput {
email: String!
password: String!
}
input RegisterInput {
email: String!
password: String!
name: String!
}
`;
```
## Resolvers Implementation
```typescript
// src/schema/resolvers/index.ts
import { Resolvers } from '../../generated/graphql';
import { userResolvers } from './user';
import { productResolvers } from './product';
import { orderResolvers } from './order';
import { authResolvers } from './auth';
import { GraphQLDateTime, GraphQLJSON } from 'graphql-scalars';
export const resolvers: Resolvers = {
DateTime: GraphQLDateTime,
JSON: GraphQLJSON,
Query: {
...userResolvers.Query,
...productResolvers.Query,
...orderResolvers.Query,
},
Mutation: {
...authResolvers.Mutation,
...userResolvers.Mutation,
...productResolvers.Mutation,
...orderResolvers.Mutation,
},
Subscription: {
...orderResolvers.Subscription,
},
User: userResolvers.User,
Product: productResolvers.Product,
Order: orderResolvers.Order,
Category: productResolvers.Category,
};
// src/schema/resolvers/product.ts
import { Resolvers, ProductsInput } from '../../generated/graphql';
import { Context } from '../context';
import { AuthenticationError, ForbiddenError } from 'apollo-server-errors';
export const productResolvers: Resolvers<Context> = {
Query: {
product: async (_, { id }, { dataSources }) => {
return dataSources.productAPI.getById(id);
},
products: async (_, { input }, { dataSources }) => {
const { first = 20, after, ...filters } = input || {};
return dataSources.productAPI.findAll({
limit: first,
cursor: after,
...filters,
});
},
},
Mutation: {
createProduct: async (_, { input }, { user, dataSources }) => {
if (!user) throw new AuthenticationError('Not authenticated');
if (user.role !== 'ADMIN') throw new ForbiddenError('Not authorized');
return dataSources.productAPI.create(input);
},
updateProduct: async (_, { id, input }, { user, dataSources }) => {
if (!user) throw new AuthenticationError('Not authenticated');
if (user.role !== 'ADMIN') throw new ForbiddenError('Not authorized');
return dataSources.productAPI.update(id, input);
},
deleteProduct: async (_, { id }, { user, dataSources }) => {
if (!user) throw new AuthenticationError('Not authenticated');
if (user.role !== 'ADMIN') throw new ForbiddenError('Not authorized');
await dataSources.productAPI.delete(id);
return true;
},
},
Product: {
category: async (product, _, { loaders }) => {
if (!product.categoryId) return null;
return loaders.categoryLoader.load(product.categoryId);
},
},
Category: {
products: async (category, { first = 20, after }, { dataSources }) => {
return dataSources.productAPI.findByCategory(category.id, {
limit: first,
cursor: after,
});
},
},
};
```
## DataLoader Pattern
```typescript
// src/schema/loaders.ts
import DataLoader from 'dataloader';
import { db } from '../database';
export function createLoaders() {
return {
userLoader: new DataLoader<string, User | null>(async (ids) => {
const users = await db.user.findMany({
where: { id: { in: [...ids] } },
});
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id) || null);
}),
productLoader: new DataLoader<string, Product | null>(async (ids) => {
const products = await db.product.findMany({
where: { id: { in: [...ids] } },
});
const productMap = new Map(products.map((p) => [p.id, p]));
return ids.map((id) => productMap.get(id) || null);
}),
categoryLoader: new DataLoader<string, Category | null>(async (ids) => {
const categories = await db.category.findMany({
where: { id: { in: [...ids] } },
});
const categoryMap = new Map(categories.map((c) => [c.id, c]));
return ids.map((id) => categoryMap.get(id) || null);
}),
// Batch load order items for multiple orders
orderItemsLoader: new DataLoader<string, OrderItem[]>(async (orderIds) => {
const items = await db.orderItem.findMany({
where: { orderId: { in: [...orderIds] } },
include: { product: true },
});
const itemsByOrder = new Map<string, OrderItem[]>();
for (const item of items) {
const existing = itemsByOrder.get(item.orderId) || [];
existing.push(item);
itemsByOrder.set(item.orderId, existing);
}
return orderIds.map((id) => itemsByOrder.get(id) || []);
}),
};
}
export type Loaders = ReturnType<typeof createLoaders>;
```
## Context and Server Setup
```typescript
// src/schema/context.ts
import { Request } from 'express';
import { createLoaders, Loaders } from './loaders';
import { verifyToken } from '../auth';
export interface Context {
user: { id: string; email: string; role: string } | null;
loaders: Loaders;
dataSources: DataSources;
}
export async function createContext({ req }: { req: Request }): Promise<Context> {
let user = null;
const authHeader = req.headers.authorization;
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7);
try {
user = verifyToken(token);
} catch {
// Invalid token, user remains null
}
}
return {
user,
loaders: createLoaders(),
dataSources: {
productAPI: new ProductAPI(),
userAPI: new UserAPI(),
orderAPI: new OrderAPI(),
},
};
}
// src/server.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './schema/resolvers';
import { createContext } from './schema/context';
async function start() {
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
app.use(
'/graphql',
express.json(),
expressMiddleware(server, {
context: createContext,
})
);
app.listen(4000, () => {
console.log('GraphQL server running at http://localhost:4000/graphql');
});
}
start();
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these GraphQL patterns: Use DataLoaders to prevent N+1 queries. Implement cursor-based pagination for lists. Design schemas with client needs in mind. Add proper authentication and authorization. Use code generation for type safety.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.