Xata is a serverless database with Postgres under the hood, plus built-in full-text search and AI — getXataClient() returns a fully typed client generated from your schema. db.users.create({ name, email }) inserts a record and returns it. db.users.read(id) fetches by ID. db.users.update(id, { name: "new" }) patches. db.users.delete(id) removes. db.users.getMany({ filter: { plan: "pro" }, sort: { createdAt: "desc" }, pagination: { size: 20, offset: 0 } }) lists with filter, sort, and pagination. Full-text search: db.products.search("wireless headphones", { target: ["name", "description"], boosters: [{ numericBooster: { column: "rating", factor: 5 } }] }). Vector search: db.products.vectorSearch("embedding", queryVector, { size: 10 }). AI ask: db.products.ask("What products are good for running?", { rules: ["Only answer about products in the database"] }) uses Xata’s LLM integration to query your data with natural language. Schema branching: xata branch create preview forks schema like Git — safe for preview deploys. File attachments: db.docs.create({ title, file: { name, base64Content, mediaType } }). Claude Code generates Xata typed clients, search queries, and vector similarity pipelines.
CLAUDE.md for Xata
## Xata Stack
- SDK: @xata.io/client >= 0.30
- Init: import { getXataClient } from "@/lib/xata"; const xata = getXataClient()
- Get: const user = await xata.db.users.read(id)
- Create: const user = await xata.db.users.create({ name, email })
- Update: const user = await xata.db.users.update(id, { plan: "pro" })
- Delete: await xata.db.users.delete(id)
- List: const { records } = await xata.db.users.getMany({ filter: { active: true }, sort: { createdAt: "desc" }, pagination: { size: 20 } })
- Search: const results = await xata.db.products.search(query, { target: ["name", "description"] })
- Ask AI: const { answer } = await xata.db.docs.ask(question, { rules: ["Answer from docs only"] })
Xata Client Setup
// lib/xata.ts — Xata client (generated by xata init, hand-maintained here)
import { buildClient, type BaseClientOptions, type SchemaInference, type XataRecord } from "@xata.io/client"
// Schema definition — generated by `xata pull` from your Xata dashboard
const tables = [
{
name: "users",
columns: [
{ name: "name", type: "string" as const },
{ name: "email", type: "email" as const },
{ name: "plan", type: "string" as const },
{ name: "active", type: "bool" as const },
{ name: "createdAt", type: "datetime" as const },
],
},
{
name: "products",
columns: [
{ name: "name", type: "string" as const },
{ name: "description", type: "text" as const },
{ name: "price", type: "float" as const },
{ name: "category", type: "string" as const },
{ name: "rating", type: "float" as const },
{ name: "inStock", type: "bool" as const },
{ name: "embedding", type: "vector", vector: { dimension: 1536 } as any },
],
},
{
name: "docs",
columns: [
{ name: "title", type: "string" as const },
{ name: "content", type: "text" as const },
{ name: "source", type: "string" as const },
{ name: "file", type: "file" as const },
],
},
] as const
type DatabaseSchema = SchemaInference<typeof tables>
const DatabaseClient = buildClient()
export class XataClient extends DatabaseClient<DatabaseSchema> {
constructor(options?: BaseClientOptions) {
super({
databaseURL: process.env.XATA_DATABASE_URL!,
apiKey: process.env.XATA_API_KEY!,
branch: process.env.XATA_BRANCH ?? "main",
...options,
})
}
}
let instance: XataClient | undefined
export function getXataClient(): XataClient {
if (!instance) instance = new XataClient()
return instance
}
CRUD and Search Operations
// lib/xata/products.ts — typed product operations
import { getXataClient } from "@/lib/xata"
const xata = getXataClient()
export type Product = {
id: string
name: string
description: string
price: number
category: string
rating: number
inStock: boolean
}
export async function createProduct(
data: Omit<Product, "id">,
): Promise<Product> {
const record = await xata.db.products.create(data)
return record as unknown as Product
}
export async function getProduct(id: string): Promise<Product | null> {
const record = await xata.db.products.read(id)
return record as unknown as Product | null
}
export async function listProducts(options: {
category?: string
inStock?: boolean
minPrice?: number
maxPrice?: number
page?: number
size?: number
} = {}): Promise<{ records: Product[]; totalCount?: number }> {
const { category, inStock, minPrice, maxPrice, page = 1, size = 20 } = options
const result = await xata.db.products.getMany({
filter: {
...(category !== undefined ? { category } : {}),
...(inStock !== undefined ? { inStock } : {}),
...(minPrice !== undefined ? { price: { $ge: minPrice } } : {}),
...(maxPrice !== undefined ? { price: { $le: maxPrice } } : {}),
},
sort: { rating: "desc" },
pagination: { size, offset: (page - 1) * size },
columns: ["id", "name", "description", "price", "category", "rating", "inStock"],
})
return { records: result.records as unknown as Product[] }
}
/** Full-text search with boosters */
export async function searchProducts(
query: string,
category?: string,
): Promise<Product[]> {
const results = await xata.db.products.search(query, {
target: ["name", "description"],
filter: category ? { category } : undefined,
boosters: [
{ numericBooster: { column: "rating", factor: 3 } },
{ valueBooster: { column: "inStock", value: true, factor: 2 } },
],
highlight: { enabled: true },
})
return results.records as unknown as Product[]
}
/** Vector similarity search */
export async function semanticSearch(
queryEmbedding: number[],
limit = 10,
): Promise<Product[]> {
const results = await xata.db.products.vectorSearch("embedding", queryEmbedding, {
size: limit,
})
return results.records as unknown as Product[]
}
/** Natural language Q&A over the products table */
export async function askProducts(question: string): Promise<{
answer: string | null
records: Product[]
}> {
const result = await xata.db.products.ask(question, {
rules: [
"Only answer about products in the database",
"Mention specific product names and prices when relevant",
"If no products match, say so clearly",
],
searchType: "vector",
})
return {
answer: result.answer ?? null,
records: (result.records ?? []) as unknown as Product[],
}
}
export async function updateProductStock(id: string, inStock: boolean): Promise<void> {
await xata.db.products.update(id, { inStock })
}
export async function deleteProduct(id: string): Promise<void> {
await xata.db.products.delete(id)
}
Next.js Search API
// app/api/products/search/route.ts — Xata full-text and AI search
import { NextResponse } from "next/server"
import { z } from "zod"
import { searchProducts, askProducts } from "@/lib/xata/products"
const Schema = z.object({
q: z.string().min(1).max(200),
category: z.string().optional(),
ai: z.coerce.boolean().default(false),
})
export async function GET(req: Request) {
const url = new URL(req.url)
const { q, category, ai } = Schema.parse(Object.fromEntries(url.searchParams))
if (ai) {
const result = await askProducts(q)
return NextResponse.json(result)
}
const products = await searchProducts(q, category)
return NextResponse.json({ records: products })
}
For the Supabase alternative when needing a more complete open-source BaaS platform with Auth, Storage, Realtime subscriptions, Edge Functions, and a hosted Postgres with the full PostgreSQL extension ecosystem — Supabase covers a broader set of backend primitives while Xata’s unique value is the built-in full-text search index, vector search, and the AI ask feature that lets you query your data in natural language without extra infrastructure, see the Supabase guide. For the PlanetScale alternative when needing a serverless MySQL-compatible database with Vitess-powered horizontal sharding, schema deploy requests as a workflow, and branching — PlanetScale uses MySQL while Xata uses PostgreSQL under the hood with a developer-friendly TypeScript SDK and native search/vector capabilities baked in, see the PlanetScale guide. The Claude Skills 360 bundle includes Xata skill sets covering typed queries, full-text search, and AI Q&A. Start with the free tier to try serverless database generation.