Build and deploy serverless functions with best practices
# Serverless Functions Guide for Google Antigravity
Build scalable serverless functions using Vercel, AWS Lambda, and Cloudflare Workers in your Google Antigravity projects. This guide covers function design, cold start optimization, and production patterns.
## Vercel Serverless Functions
Create optimized API routes in Next.js:
```typescript
// src/app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db } from "@/lib/database";
import { rateLimit } from "@/lib/rate-limit";
import { verifyAuth } from "@/lib/auth";
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(["user", "admin"]).default("user"),
});
// Configure edge runtime for faster cold starts
export const runtime = "edge";
// Enable ISR caching for GET requests
export const revalidate = 60;
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get("page") || "1");
const limit = parseInt(searchParams.get("limit") || "20");
const search = searchParams.get("search") || "";
const offset = (page - 1) * limit;
const [users, total] = await Promise.all([
db.user.findMany({
where: search
? {
OR: [
{ name: { contains: search, mode: "insensitive" } },
{ email: { contains: search, mode: "insensitive" } },
],
}
: undefined,
select: {
id: true,
name: true,
email: true,
role: true,
createdAt: true,
},
skip: offset,
take: limit,
orderBy: { createdAt: "desc" },
}),
db.user.count(),
]);
return NextResponse.json({
users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
} catch (error) {
console.error("GET /api/users error:", error);
return NextResponse.json(
{ error: "Failed to fetch users" },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
// Rate limiting
const ip = request.headers.get("x-forwarded-for") || "unknown";
const { success, remaining } = await rateLimit.check(ip, 10, "1m");
if (!success) {
return NextResponse.json(
{ error: "Rate limit exceeded" },
{
status: 429,
headers: { "X-RateLimit-Remaining": remaining.toString() },
}
);
}
// Authentication
const auth = await verifyAuth(request);
if (!auth || auth.role !== "admin") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Validation
const body = await request.json();
const validatedData = createUserSchema.parse(body);
// Check for existing user
const existing = await db.user.findUnique({
where: { email: validatedData.email },
});
if (existing) {
return NextResponse.json(
{ error: "User already exists" },
{ status: 409 }
);
}
// Create user
const user = await db.user.create({
data: validatedData,
select: {
id: true,
name: true,
email: true,
role: true,
createdAt: true,
},
});
return NextResponse.json({ user }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Validation failed", details: error.errors },
{ status: 400 }
);
}
console.error("POST /api/users error:", error);
return NextResponse.json(
{ error: "Failed to create user" },
{ status: 500 }
);
}
}
```
## AWS Lambda with TypeScript
Build Lambda functions with proper typing:
```typescript
// src/handlers/processOrder.ts
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from "aws-lambda";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
import { z } from "zod";
// Initialize clients outside handler for connection reuse
const dynamoClient = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(dynamoClient);
const sqsClient = new SQSClient({});
const orderSchema = z.object({
customerId: z.string().uuid(),
items: z.array(
z.object({
productId: z.string(),
quantity: z.number().positive(),
price: z.number().positive(),
})
).min(1),
shippingAddress: z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zip: z.string(),
country: z.string(),
}),
});
type OrderInput = z.infer<typeof orderSchema>;
interface Order extends OrderInput {
orderId: string;
status: "pending" | "processing" | "shipped" | "delivered";
total: number;
createdAt: string;
}
export async function handler(
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> {
// Enable connection reuse
context.callbackWaitsForEmptyEventLoop = false;
const requestId = context.awsRequestId;
try {
if (!event.body) {
return response(400, { error: "Request body is required" });
}
const body = JSON.parse(event.body);
const validatedData = orderSchema.parse(body);
// Calculate total
const total = validatedData.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const order: Order = {
...validatedData,
orderId: requestId,
status: "pending",
total,
createdAt: new Date().toISOString(),
};
// Save to DynamoDB
await docClient.send(
new PutCommand({
TableName: process.env.ORDERS_TABLE!,
Item: order,
ConditionExpression: "attribute_not_exists(orderId)",
})
);
// Send to processing queue
await sqsClient.send(
new SendMessageCommand({
QueueUrl: process.env.PROCESSING_QUEUE_URL!,
MessageBody: JSON.stringify({ orderId: order.orderId }),
MessageGroupId: order.customerId,
})
);
return response(201, { order });
} catch (error) {
if (error instanceof z.ZodError) {
return response(400, { error: "Validation failed", details: error.errors });
}
console.error("Error processing order:", error);
return response(500, { error: "Failed to process order" });
}
}
function response(statusCode: number, body: object): APIGatewayProxyResult {
return {
statusCode,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify(body),
};
}
```
## Cloudflare Workers
Deploy edge functions globally:
```typescript
// src/worker.ts
export interface Env {
KV_STORE: KVNamespace;
DATABASE: D1Database;
RATE_LIMITER: DurableObjectNamespace;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
// Route handling
if (url.pathname === "/api/data" && request.method === "GET") {
return handleGetData(request, env, ctx);
}
if (url.pathname === "/api/data" && request.method === "POST") {
return handlePostData(request, env);
}
return new Response("Not Found", { status: 404 });
} catch (error) {
console.error("Worker error:", error);
return new Response(JSON.stringify({ error: "Internal error" }), {
status: 500,
headers: { "Content-Type": "application/json", ...corsHeaders },
});
}
},
};
async function handleGetData(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const cacheKey = new URL(request.url).toString();
// Check KV cache
const cached = await env.KV_STORE.get(cacheKey, "json");
if (cached) {
return jsonResponse(cached);
}
// Query D1 database
const { results } = await env.DATABASE.prepare(
"SELECT * FROM items ORDER BY created_at DESC LIMIT 100"
).all();
// Cache for 5 minutes
ctx.waitUntil(
env.KV_STORE.put(cacheKey, JSON.stringify(results), { expirationTtl: 300 })
);
return jsonResponse(results);
}
```
Google Antigravity generates optimized serverless functions with proper error handling, caching strategies, and infrastructure patterns for scalable cloud-native applications.This Serverless 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 serverless 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 Serverless projects, consider mentioning your framework version, coding style, and any specific libraries you're using.