Building REST & GraphQL APIs with Google Antigravity: Complete Backend Development Guide | Antigravity AI Directory | Google Antigravity Directory
Building REST & GraphQL APIs with Google Antigravi... Tutorials Building REST & GraphQL APIs with Google Antigravity: Complete Backend Development Guide Building REST & GraphQL APIs with Google Antigravity: Complete Backend Development Guide
API development is where Google Antigravity truly shines. Gemini 3 can generate complete API endpoints, handle authentication, create validation schemas, and even write documentation. This guide covers building production-grade APIs.
API Architecture Options
When to Use Each
Type Best For Complexity REST Simple CRUD, public APIs Low GraphQL Complex data relationships Medium tRPC Type-safe full-stack apps Low gRPC Microservices, high performance High
REST API with Next.js
Basic API Route
You: "Create a REST API for managing blog posts with CRUD operations"
Gemini 3 generates:
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { z } from 'zod';
// Validation schemas
const createPostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
});
const querySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(10),
published: z.enum(['true', 'false']).optional(),
});
// GET /api/posts - List posts with pagination
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const query = querySchema.parse(Object.fromEntries(searchParams));
const supabase = createClient();
let queryBuilder = supabase
.from('posts')
.select('*, author:profiles(username, avatar_url)', { count: 'exact' });
if (query.published !== undefined) {
queryBuilder = queryBuilder.eq('published', query.published === 'true');
}
const { data, error, count } = await queryBuilder
.order('created_at', { ascending: false })
.range((query.page - 1) * query.limit, query.page * query.limit - 1);
if (error) throw error;
return NextResponse.json({
data,
pagination: {
page: query.page,
limit: query.limit,
total: count ?? 0,
totalPages: Math.ceil((count ?? 0) / query.limit),
},
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
console.error('GET /api/posts error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// POST /api/posts - Create new post
export async function POST(request: NextRequest) {
try {
const supabase = createClient();
// Check authentication
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Validate request body
const body = await request.json();
const validatedData = createPostSchema.parse(body);
// Create post
const { data, error } = await supabase
.from('posts')
.insert({
...validatedData,
author_id: user.id,
slug: validatedData.title.toLowerCase().replace(/\s+/g, '-'),
})
.select()
.single();
if (error) throw error;
return NextResponse.json({ data }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
console.error('POST /api/posts error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Related Posts Antigravity + Supabase: Build a Complete App in 30 Minutes (Step-by-Step Tutorial) Build a full-stack application with Google Antigravity and Supabase in under 30 minutes. This hands-on tutorial covers authentication, database, real-time subscriptions, and deployment—complete with copy-paste prompts.
Next.js 15 + Antigravity: The Ultimate React Developer Workflow (2025) Master the perfect Next.js 15 development workflow with Google Antigravity. Learn Server Components, Server Actions, App Router patterns, and production-ready prompts that 10x your React development speed.
Antigravity for Python Developers: Django, FastAPI & Flask Complete Workflows (2025) Master Google Antigravity for Python development. Complete guide covering Django, FastAPI, and Flask workflows with production-ready prompts, MCP server integrations, and real-world examples.
Dynamic Route for Single Resource // app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { z } from 'zod';
const updatePostSchema = z.object({
title: z.string().min(1).max(200).optional(),
content: z.string().min(1).optional(),
published: z.boolean().optional(),
});
interface Params {
params: { id: string };
}
// GET /api/posts/:id
export async function GET(request: NextRequest, { params }: Params) {
try {
const supabase = createClient();
const { data, error } = await supabase
.from('posts')
.select('*, author:profiles(username, avatar_url)')
.eq('id', params.id)
.single();
if (error) {
if (error.code === 'PGRST116') {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
throw error;
}
return NextResponse.json({ data });
} catch (error) {
console.error('GET /api/posts/[id] error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// PATCH /api/posts/:id
export async function PATCH(request: NextRequest, { params }: Params) {
try {
const supabase = createClient();
// Check authentication
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Check ownership
const { data: existingPost } = await supabase
.from('posts')
.select('author_id')
.eq('id', params.id)
.single();
if (!existingPost) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
if (existingPost.author_id !== user.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Validate and update
const body = await request.json();
const validatedData = updatePostSchema.parse(body);
const { data, error } = await supabase
.from('posts')
.update({ ...validatedData, updated_at: new Date().toISOString() })
.eq('id', params.id)
.select()
.single();
if (error) throw error;
return NextResponse.json({ data });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
console.error('PATCH /api/posts/[id] error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// DELETE /api/posts/:id
export async function DELETE(request: NextRequest, { params }: Params) {
try {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Check ownership before delete
const { data: existingPost } = await supabase
.from('posts')
.select('author_id')
.eq('id', params.id)
.single();
if (!existingPost) {
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
}
if (existingPost.author_id !== user.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const { error } = await supabase
.from('posts')
.delete()
.eq('id', params.id);
if (error) throw error;
return new NextResponse(null, { status: 204 });
} catch (error) {
console.error('DELETE /api/posts/[id] error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
GraphQL API
Setting Up GraphQL npm install @apollo/server graphql @as-integrations/next
Schema Definition You: "Create a GraphQL schema for a blog with posts, comments, and users"
// lib/graphql/schema.ts
import { gql } from 'graphql-tag';
export const typeDefs = gql`
scalar DateTime
type User {
id: ID!
email: String!
username: String!
avatar_url: String
posts: [Post!]!
comments: [Comment!]!
created_at: DateTime!
}
type Post {
id: ID!
title: String!
slug: String!
content: String!
excerpt: String
published: Boolean!
author: User!
comments: [Comment!]!
created_at: DateTime!
updated_at: DateTime!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
created_at: DateTime!
}
type PostConnection {
nodes: [Post!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
input CreateCommentInput {
postId: ID!
content: String!
}
type Query {
# Posts
posts(
first: Int = 10
after: String
published: Boolean
): PostConnection!
post(id: ID, slug: String): Post
# Users
me: User
user(id: ID!): User
}
type Mutation {
# Posts
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
# Comments
createComment(input: CreateCommentInput!): Comment!
deleteComment(id: ID!): Boolean!
}
`;
GraphQL Resolvers // lib/graphql/resolvers.ts
import { createClient } from '@/lib/supabase/server';
import { GraphQLError } from 'graphql';
export const resolvers = {
Query: {
posts: async (_: unknown, args: { first: number; after?: string; published?: boolean }) => {
const supabase = createClient();
let query = supabase
.from('posts')
.select('*', { count: 'exact' });
if (args.published !== undefined) {
query = query.eq('published', args.published);
}
const { data, error, count } = await query
.order('created_at', { ascending: false })
.limit(args.first);
if (error) throw new GraphQLError(error.message);
return {
nodes: data,
totalCount: count ?? 0,
pageInfo: {
hasNextPage: (count ?? 0) > args.first,
hasPreviousPage: !!args.after,
},
};
},
post: async (_: unknown, args: { id?: string; slug?: string }) => {
const supabase = createClient();
let query = supabase.from('posts').select('*');
if (args.id) {
query = query.eq('id', args.id);
} else if (args.slug) {
query = query.eq('slug', args.slug);
} else {
throw new GraphQLError('Either id or slug must be provided');
}
const { data, error } = await query.single();
if (error) return null;
return data;
},
me: async (_: unknown, __: unknown, context: { userId?: string }) => {
if (!context.userId) return null;
const supabase = createClient();
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', context.userId)
.single();
return data;
},
},
Mutation: {
createPost: async (
_: unknown,
args: { input: { title: string; content: string; published?: boolean } },
context: { userId?: string }
) => {
if (!context.userId) {
throw new GraphQLError('Unauthorized', {
extensions: { code: 'UNAUTHORIZED' },
});
}
const supabase = createClient();
const { data, error } = await supabase
.from('posts')
.insert({
...args.input,
author_id: context.userId,
slug: args.input.title.toLowerCase().replace(/\s+/g, '-'),
})
.select()
.single();
if (error) throw new GraphQLError(error.message);
return data;
},
updatePost: async (
_: unknown,
args: { id: string; input: { title?: string; content?: string; published?: boolean } },
context: { userId?: string }
) => {
if (!context.userId) {
throw new GraphQLError('Unauthorized');
}
const supabase = createClient();
// Check ownership
const { data: existing } = await supabase
.from('posts')
.select('author_id')
.eq('id', args.id)
.single();
if (!existing || existing.author_id !== context.userId) {
throw new GraphQLError('Forbidden');
}
const { data, error } = await supabase
.from('posts')
.update(args.input)
.eq('id', args.id)
.select()
.single();
if (error) throw new GraphQLError(error.message);
return data;
},
deletePost: async (_: unknown, args: { id: string }, context: { userId?: string }) => {
if (!context.userId) {
throw new GraphQLError('Unauthorized');
}
const supabase = createClient();
const { error } = await supabase
.from('posts')
.delete()
.eq('id', args.id)
.eq('author_id', context.userId);
if (error) throw new GraphQLError(error.message);
return true;
},
},
// Field resolvers for relationships
Post: {
author: async (post: { author_id: string }) => {
const supabase = createClient();
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', post.author_id)
.single();
return data;
},
comments: async (post: { id: string }) => {
const supabase = createClient();
const { data } = await supabase
.from('comments')
.select('*')
.eq('post_id', post.id)
.order('created_at', { ascending: true });
return data ?? [];
},
},
User: {
posts: async (user: { id: string }) => {
const supabase = createClient();
const { data } = await supabase
.from('posts')
.select('*')
.eq('author_id', user.id);
return data ?? [];
},
},
};
GraphQL API Route // app/api/graphql/route.ts
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateNextHandler } from '@as-integrations/next';
import { NextRequest } from 'next/server';
import { typeDefs } from '@/lib/graphql/schema';
import { resolvers } from '@/lib/graphql/resolvers';
import { createClient } from '@/lib/supabase/server';
const server = new ApolloServer({
typeDefs,
resolvers,
});
const handler = startServerAndCreateNextHandler(server, {
context: async (req: NextRequest) => {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
return {
userId: user?.id,
};
},
});
export { handler as GET, handler as POST };
tRPC for Full-Stack TypeScript
Setting Up tRPC npm install @trpc/server @trpc/client @trpc/react-query @trpc/next
tRPC Router // lib/trpc/router.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
import { createClient } from '@/lib/supabase/server';
import superjson from 'superjson';
const t = initTRPC.context<{ userId?: string }>().create({
transformer: superjson,
});
const publicProcedure = t.procedure;
const protectedProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { ...ctx, userId: ctx.userId } });
});
export const appRouter = t.router({
// Public queries
posts: t.router({
list: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(10),
cursor: z.string().nullish(),
published: z.boolean().optional(),
}))
.query(async ({ input }) => {
const supabase = createClient();
let query = supabase
.from('posts')
.select('*, author:profiles(username, avatar_url)')
.order('created_at', { ascending: false })
.limit(input.limit + 1);
if (input.published !== undefined) {
query = query.eq('published', input.published);
}
if (input.cursor) {
query = query.lt('created_at', input.cursor);
}
const { data, error } = await query;
if (error) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: error.message });
let nextCursor: string | undefined;
if (data.length > input.limit) {
const nextItem = data.pop();
nextCursor = nextItem?.created_at;
}
return { items: data, nextCursor };
}),
byId: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input }) => {
const supabase = createClient();
const { data, error } = await supabase
.from('posts')
.select('*, author:profiles(username, avatar_url)')
.eq('id', input.id)
.single();
if (error) throw new TRPCError({ code: 'NOT_FOUND' });
return data;
}),
// Protected mutations
create: protectedProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
}))
.mutation(async ({ ctx, input }) => {
const supabase = createClient();
const { data, error } = await supabase
.from('posts')
.insert({
...input,
author_id: ctx.userId,
slug: input.title.toLowerCase().replace(/\s+/g, '-'),
})
.select()
.single();
if (error) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: error.message });
return data;
}),
update: protectedProcedure
.input(z.object({
id: z.string().uuid(),
title: z.string().min(1).max(200).optional(),
content: z.string().min(1).optional(),
published: z.boolean().optional(),
}))
.mutation(async ({ ctx, input }) => {
const supabase = createClient();
const { id, ...updateData } = input;
const { data, error } = await supabase
.from('posts')
.update(updateData)
.eq('id', id)
.eq('author_id', ctx.userId)
.select()
.single();
if (error) throw new TRPCError({ code: 'FORBIDDEN' });
return data;
}),
delete: protectedProcedure
.input(z.object({ id: z.string().uuid() }))
.mutation(async ({ ctx, input }) => {
const supabase = createClient();
const { error } = await supabase
.from('posts')
.delete()
.eq('id', input.id)
.eq('author_id', ctx.userId);
if (error) throw new TRPCError({ code: 'FORBIDDEN' });
return { success: true };
}),
}),
});
export type AppRouter = typeof appRouter;
API Documentation
OpenAPI/Swagger Generation You: "Generate OpenAPI spec for the posts API"
# openapi.yaml
openapi: 3.0.3
info:
title: Blog API
version: 1.0.0
description: REST API for blog posts management
servers:
- url: https://api.example.com/v1
description: Production
- url: http://localhost:3000/api
description: Development
paths:
/posts:
get:
summary: List all posts
tags: [Posts]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 10
maximum: 100
- name: published
in: query
schema:
type: boolean
responses:
'200':
description: Paginated list of posts
content:
application/json:
schema:
$ref: '#/components/schemas/PostListResponse'
post:
summary: Create a new post
tags: [Posts]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreatePostInput'
responses:
'201':
description: Post created
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
'401':
$ref: '#/components/responses/Unauthorized'
components:
schemas:
Post:
type: object
properties:
id:
type: string
format: uuid
title:
type: string
slug:
type: string
content:
type: string
published:
type: boolean
created_at:
type: string
format: date-time
author:
$ref: '#/components/schemas/Author'
CreatePostInput:
type: object
required: [title, content]
properties:
title:
type: string
minLength: 1
maxLength: 200
content:
type: string
minLength: 1
published:
type: boolean
default: false
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
Conclusion Google Antigravity accelerates API development with:
Complete CRUD endpoint generation
Type-safe schemas and validation
Authentication/authorization patterns
Auto-generated documentation
Use Zod for runtime validation
Implement proper error handling
Add authentication at the middleware level
Generate OpenAPI docs for REST APIs
Consider tRPC for full-stack TypeScript
Build APIs faster with AI assistance. Focus on business logic, not boilerplate.
Link Slot Gacor Mahjong333 Link Alternatif Resmi