From f474731eb91c87a3f39604c8ff65c4f616be32a3 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Tue, 1 Jul 2025 15:42:03 -0400 Subject: [PATCH 01/13] Add Quickstarts section and backend/frontend/ORM guides for Xata PostgreSQL --- docs/config.json | 102 +++++ docs/quickstarts/astro.mdx | 197 +++++++++ docs/quickstarts/django.mdx | 355 +++++++++++++++ docs/quickstarts/drizzle.mdx | 220 ++++++++++ docs/quickstarts/go.mdx | 365 ++++++++++++++++ docs/quickstarts/gorm.mdx | 681 +++++++++++++++++++++++++++++ docs/quickstarts/java.mdx | 495 +++++++++++++++++++++ docs/quickstarts/jpa.mdx | 722 +++++++++++++++++++++++++++++++ docs/quickstarts/laravel.mdx | 406 +++++++++++++++++ docs/quickstarts/nextjs.mdx | 246 +++++++++++ docs/quickstarts/nodejs.mdx | 258 +++++++++++ docs/quickstarts/nuxt.mdx | 264 +++++++++++ docs/quickstarts/prisma.mdx | 532 +++++++++++++++++++++++ docs/quickstarts/python.mdx | 592 +++++++++++++++++++++++++ docs/quickstarts/quickstarts.mdx | 71 +++ docs/quickstarts/rails.mdx | 348 +++++++++++++++ docs/quickstarts/remix.mdx | 167 +++++++ docs/quickstarts/rust.mdx | 361 ++++++++++++++++ docs/quickstarts/sqlalchemy.mdx | 452 +++++++++++++++++++ docs/quickstarts/svelte.mdx | 184 ++++++++ docs/quickstarts/typeorm.mdx | 585 +++++++++++++++++++++++++ 21 files changed, 7603 insertions(+) create mode 100644 docs/quickstarts/astro.mdx create mode 100644 docs/quickstarts/django.mdx create mode 100644 docs/quickstarts/drizzle.mdx create mode 100644 docs/quickstarts/go.mdx create mode 100644 docs/quickstarts/gorm.mdx create mode 100644 docs/quickstarts/java.mdx create mode 100644 docs/quickstarts/jpa.mdx create mode 100644 docs/quickstarts/laravel.mdx create mode 100644 docs/quickstarts/nextjs.mdx create mode 100644 docs/quickstarts/nodejs.mdx create mode 100644 docs/quickstarts/nuxt.mdx create mode 100644 docs/quickstarts/prisma.mdx create mode 100644 docs/quickstarts/python.mdx create mode 100644 docs/quickstarts/quickstarts.mdx create mode 100644 docs/quickstarts/rails.mdx create mode 100644 docs/quickstarts/remix.mdx create mode 100644 docs/quickstarts/rust.mdx create mode 100644 docs/quickstarts/sqlalchemy.mdx create mode 100644 docs/quickstarts/svelte.mdx create mode 100644 docs/quickstarts/typeorm.mdx diff --git a/docs/config.json b/docs/config.json index 3b6d582..a15c9e3 100644 --- a/docs/config.json +++ b/docs/config.json @@ -5,6 +5,108 @@ "href": "/getting-started", "file": "docs/getting-started.mdx" }, + { + "title": "Quickstarts", + "href": "/quickstarts", + "file": "docs/quickstarts/quickstarts.mdx", + "items": [ + { + "title": "Drizzle", + "href": "/quickstarts/drizzle", + "file": "docs/quickstarts/drizzle.mdx" + }, + { + "title": "Next.js", + "href": "/quickstarts/nextjs", + "file": "docs/quickstarts/nextjs.mdx" + }, + { + "title": "Nuxt", + "href": "/quickstarts/nuxt", + "file": "docs/quickstarts/nuxt.mdx" + }, + { + "title": "Astro", + "href": "/quickstarts/astro", + "file": "docs/quickstarts/astro.mdx" + }, + { + "title": "SvelteKit", + "href": "/quickstarts/svelte", + "file": "docs/quickstarts/svelte.mdx" + }, + { + "title": "Remix", + "href": "/quickstarts/remix", + "file": "docs/quickstarts/remix.mdx" + }, + { + "title": "Node.js", + "href": "/quickstarts/nodejs", + "file": "docs/quickstarts/nodejs.mdx" + }, + { + "title": "Django", + "href": "/quickstarts/django", + "file": "docs/quickstarts/django.mdx" + }, + { + "title": "Rails", + "href": "/quickstarts/rails", + "file": "docs/quickstarts/rails.mdx" + }, + { + "title": "Go", + "href": "/quickstarts/go", + "file": "docs/quickstarts/go.mdx" + }, + { + "title": "Rust", + "href": "/quickstarts/rust", + "file": "docs/quickstarts/rust.mdx" + }, + { + "title": "Laravel", + "href": "/quickstarts/laravel", + "file": "docs/quickstarts/laravel.mdx" + }, + { + "title": "Java", + "href": "/quickstarts/java", + "file": "docs/quickstarts/java.mdx" + }, + { + "title": "Python", + "href": "/quickstarts/python", + "file": "docs/quickstarts/python.mdx" + }, + { + "title": "SQLAlchemy", + "href": "/quickstarts/sqlalchemy", + "file": "docs/quickstarts/sqlalchemy.mdx" + }, + { + "title": "Prisma", + "href": "/quickstarts/prisma", + "file": "docs/quickstarts/prisma.mdx" + }, + { + "title": "TypeORM", + "href": "/quickstarts/typeorm", + "file": "docs/quickstarts/typeorm.mdx" + }, + { + "title": "GORM", + "href": "/quickstarts/gorm", + "file": "docs/quickstarts/gorm.mdx" + }, + { + "title": "JPA", + "href": "/quickstarts/jpa", + "file": "docs/quickstarts/jpa.mdx" + } + ] + }, { "title": "Deployment models", "href": "/deployment", diff --git a/docs/quickstarts/astro.mdx b/docs/quickstarts/astro.mdx new file mode 100644 index 0000000..3a9a704 --- /dev/null +++ b/docs/quickstarts/astro.mdx @@ -0,0 +1,197 @@ +--- +title: "Connect Astro to Xata" +description: "Learn how to connect your Astro application to Xata's PostgreSQL platform. Get started with Astro and PostgreSQL for fast, modern web apps." +--- + +# Connect Astro to Xata + +Learn how to use Astro with Xata's PostgreSQL platform. Astro is a modern static site builder that supports server-side rendering and API endpoints. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of Astro + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Astro Project + +Create a new Astro project: + +```bash +npm create astro@latest +cd my-xata-app +npm install +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL client: + +```bash +npm install postgres +npm install -D @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// src/lib/db.ts +import postgres from 'postgres'; + +const connectionString = import.meta.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create API Endpoint + +Create an API endpoint to fetch products: + +```typescript +// src/pages/api/products.ts +import type { APIRoute } from 'astro'; +import { sql } from '../../lib/db'; + +export const get: APIRoute = async () => { + try { + const products = await sql`SELECT * FROM products`; + return new Response(JSON.stringify(products), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: 'Failed to fetch products' }), { status: 500 }); + } +}; +``` + +## Create Products Page + +Create a page to display products: + +```astro +--- +// src/pages/index.astro +import { useEffect, useState } from 'react'; + +const [products, setProducts] = useState([]); +const [loading, setLoading] = useState(true); + +useEffect(() => { + fetch('/api/products') + .then(res => res.json()) + .then(data => { + setProducts(data); + setLoading(false); + }) + .catch(() => setLoading(false)); +}, []); +--- + + + + Xata E-commerce Store + + +

Xata E-commerce Store

+ {loading ? ( +
Loading products...
+ ) : ( +
+ {products.map(product => ( +
+

{product.name}

+

${product.price}

+ {product.rating && ( +
+ + {product.rating}/5 +
+ )} +
+ ))} +
+ )} + + +``` + + + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Visit `http://localhost:4321` to see your application. + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/django.mdx b/docs/quickstarts/django.mdx new file mode 100644 index 0000000..e69eda8 --- /dev/null +++ b/docs/quickstarts/django.mdx @@ -0,0 +1,355 @@ +--- +title: "Connect Django to Xata" +description: "Learn how to connect your Django application to Xata's PostgreSQL platform. Get started with Django and PostgreSQL for robust web applications." +--- + +# Connect Django to Xata + +Learn how to use Django with Xata's PostgreSQL platform. Django is a high-level Python web framework that provides excellent ORM capabilities and rapid development features. + +## Prerequisites + +- Xata account and project setup +- Python 3.8+ installed +- Basic knowledge of Python and Django + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Django Project + +Create a new Django project: + +```bash +pip install django psycopg2-binary +django-admin startproject my_xata_app +cd my_xata_app +python manage.py startapp store +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install additional dependencies: + +```bash +pip install python-decouple +``` + +## Configure Database Connection + +Update your `settings.py` to use the Xata PostgreSQL database: + +```python +# my_xata_app/settings.py +import os +from decouple import config + +# ... existing settings ... + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': config('DB_NAME', default='postgres'), + 'USER': config('DB_USER', default='postgres'), + 'PASSWORD': config('DB_PASSWORD', default=''), + 'HOST': config('DB_HOST', default='localhost'), + 'PORT': config('DB_PORT', default='5432'), + } +} + +# Add 'store' to INSTALLED_APPS +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'store', # Add this line +] +``` + +Create a `.env` file with the database components: + +```bash +# .env +DB_NAME=your_database_name +DB_USER=your_username +DB_PASSWORD=your_password +DB_HOST=your_host +DB_PORT=5432 +``` + +## Create Models + +Create your Django models in `store/models.py`: + +```python +# store/models.py +from django.db import models + +class Product(models.Model): + name = models.CharField(max_length=255) + price = models.DecimalField(max_digits=7, decimal_places=2) + rating = models.IntegerField(null=True, blank=True) + + def __str__(self): + return self.name + +class Order(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Order {self.id} - {self.created_at}" + +class OrderItem(models.Model): + order = models.ForeignKey(Order, on_delete=models.CASCADE) + product = models.ForeignKey(Product, on_delete=models.CASCADE) + qty = models.IntegerField() + + class Meta: + unique_together = ['order', 'product'] + + def __str__(self): + return f"{self.qty}x {self.product.name} in Order {self.order.id}" +``` + +## Create Views + +Create views in `store/views.py`: + +```python +# store/views.py +from django.shortcuts import render +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +import json +from .models import Product, Order, OrderItem + +def product_list(request): + products = Product.objects.all() + data = [{ + 'id': product.id, + 'name': product.name, + 'price': float(product.price), + 'rating': product.rating + } for product in products] + return JsonResponse(data, safe=False) + +def product_detail(request, product_id): + try: + product = Product.objects.get(id=product_id) + data = { + 'id': product.id, + 'name': product.name, + 'price': float(product.price), + 'rating': product.rating + } + return JsonResponse(data) + except Product.DoesNotExist: + return JsonResponse({'error': 'Product not found'}, status=404) + +@csrf_exempt +@require_http_methods(["POST"]) +def create_product(request): + try: + data = json.loads(request.body) + product = Product.objects.create( + name=data['name'], + price=data['price'], + rating=data.get('rating') + ) + return JsonResponse({ + 'id': product.id, + 'name': product.name, + 'price': float(product.price), + 'rating': product.rating + }, status=201) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + +def order_list(request): + orders = Order.objects.prefetch_related('orderitem_set__product').all() + data = [] + for order in orders: + for item in order.orderitem_set.all(): + data.append({ + 'order_id': order.id, + 'created_at': order.created_at.isoformat(), + 'product_name': item.product.name, + 'qty': item.qty, + 'total': float(item.product.price * item.qty) + }) + return JsonResponse(data, safe=False) +``` + +## Configure URLs + +Update your main `urls.py`: + +```python +# my_xata_app/urls.py +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('store.urls')), +] +``` + +Create `store/urls.py`: + +```python +# store/urls.py +from django.urls import path +from . import views + +urlpatterns = [ + path('products/', views.product_list, name='product_list'), + path('products//', views.product_detail, name='product_detail'), + path('products/create/', views.create_product, name='create_product'), + path('orders/', views.order_list, name='order_list'), +] +``` + +## Run Migrations + +Since you're using an existing database schema, you can create a migration that matches your existing tables: + +```bash +python manage.py makemigrations --empty store +``` + +Then edit the generated migration file to use `RunSQL`: + +```python +# store/migrations/0001_initial.py +from django.db import migrations + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.RunSQL(""" + CREATE TABLE IF NOT EXISTS store_product ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER NULL + ); + """), + migrations.RunSQL(""" + CREATE TABLE IF NOT EXISTS store_order ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() + ); + """), + migrations.RunSQL(""" + CREATE TABLE IF NOT EXISTS store_orderitem ( + id SERIAL PRIMARY KEY, + order_id INTEGER REFERENCES store_order(id), + product_id INTEGER REFERENCES store_product(id), + qty INTEGER NOT NULL, + UNIQUE(order_id, product_id) + ); + """), + ] +``` + +Run the migration: + +```bash +python manage.py migrate +``` + +## Run the Application + +Start your development server: + +```bash +python manage.py runserver +``` + +Your API will be available at: +- `http://localhost:8000/api/products/` +- `http://localhost:8000/api/orders/` + +## Test Your API + +You can test the endpoints using curl: + +```bash +# Get all products +curl http://localhost:8000/api/products/ + +# Create a new product +curl -X POST http://localhost:8000/api/products/create/ \ + -H "Content-Type: application/json" \ + -d '{"name":"Wireless Headphones","price":99.99,"rating":5}' + +# Get orders with products +curl http://localhost:8000/api/orders/ +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/drizzle.mdx b/docs/quickstarts/drizzle.mdx new file mode 100644 index 0000000..2c3d18c --- /dev/null +++ b/docs/quickstarts/drizzle.mdx @@ -0,0 +1,220 @@ +--- +title: "Connect Drizzle to Xata" +description: "Learn how to connect your Drizzle ORM application to Xata's PostgreSQL platform. Get started with Drizzle and PostgreSQL for type-safe database operations." +--- + +# Connect Drizzle to Xata + +Learn how to use Drizzle ORM with Xata's PostgreSQL platform. Drizzle provides excellent TypeScript support and type safety for your database operations. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of TypeScript and Drizzle ORM + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Create a new Node.js project and install Drizzle: + +```bash +npm init -y +npm install drizzle-orm postgres +npm install -D drizzle-kit @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// db/config.ts +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema'; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +const client = postgres(connectionString); +export const db = drizzle(client, { schema }); +``` + +## Define Schema + +Create your Drizzle schema that matches the Xata database: + +```typescript +// db/schema.ts +import { pgTable, serial, text, numeric, integer, timestamp } from 'drizzle-orm/pg-core'; + +export const products = pgTable('products', { + id: serial('id').primaryKey(), + name: text('name').notNull(), + price: numeric('price', { precision: 7, scale: 2 }).notNull(), + rating: integer('rating'), +}); + +export const orders = pgTable('orders', { + id: serial('id').primaryKey(), + created_at: timestamp('created_at', { withTimezone: true }).defaultNow(), +}); + +export const order_items = pgTable('order_items', { + order_id: integer('order_id').references(() => orders.id), + product_id: integer('product_id').references(() => products.id), + qty: integer('qty').notNull(), +}); + +export type Product = typeof products.$inferSelect; +export type NewProduct = typeof products.$inferInsert; +export type Order = typeof orders.$inferSelect; +export type NewOrder = typeof orders.$inferInsert; +export type OrderItem = typeof order_items.$inferSelect; +export type NewOrderItem = typeof order_items.$inferInsert; +``` + +## Build the Application + +Create a simple application to demonstrate CRUD operations: + +```typescript +// app.ts +import { db } from './db/config'; +import { products, orders, order_items } from './db/schema'; +import { eq, desc } from 'drizzle-orm'; + +async function main() { + // Insert a new product + const [newProduct] = await db.insert(products).values({ + name: 'Wireless Headphones', + price: 99.99, + rating: 5, + }).returning(); + + console.log('Created product:', newProduct); + + // Query products with rating + const topProducts = await db + .select() + .from(products) + .where(eq(products.rating, 5)) + .orderBy(desc(products.price)); + + console.log('Top rated products:', topProducts); + + // Create an order with items + const [newOrder] = await db.insert(orders).values({}).returning(); + + await db.insert(order_items).values({ + order_id: newOrder.id, + product_id: newProduct.id, + qty: 2, + }); + + // Query orders with product details + const ordersWithProducts = await db + .select({ + order_id: orders.id, + created_at: orders.created_at, + product_name: products.name, + qty: order_items.qty, + total: db.$all(products.price).mul(order_items.qty), + }) + .from(orders) + .innerJoin(order_items, eq(orders.id, order_items.order_id)) + .innerJoin(products, eq(order_items.product_id, products.id)); + + console.log('Orders with products:', ordersWithProducts); +} + +main().catch(console.error); +``` + +## Environment Setup + +Create a `.env` file with your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +## Run the Application + +Execute your application: + +```bash +npm run dev +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/go.mdx b/docs/quickstarts/go.mdx new file mode 100644 index 0000000..1e340f8 --- /dev/null +++ b/docs/quickstarts/go.mdx @@ -0,0 +1,365 @@ +--- +title: "Connect Go to Xata" +description: "Learn how to connect your Go application to Xata's PostgreSQL platform. Get started with Go and PostgreSQL for high-performance backend services." +--- + +# Connect Go to Xata + +Learn how to use Go with Xata's PostgreSQL platform. Go provides excellent performance and concurrency features for building scalable backend services. + +## Prerequisites + +- Xata account and project setup +- Go 1.19+ installed +- Basic knowledge of Go + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Go Project + +Create a new Go project: + +```bash +mkdir my-xata-app +cd my-xata-app +go mod init my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the required packages: + +```bash +go get github.com/lib/pq +go get github.com/gorilla/mux +go get github.com/joho/godotenv +``` + +## Configure Database Connection + +Create a database configuration file: + +```go +// db/db.go +package db + +import ( + "database/sql" + "fmt" + "log" + "os" + + _ "github.com/lib/pq" +) + +var DB *sql.DB + +func InitDB() { + connectionString := os.Getenv("DATABASE_URL") + if connectionString == "" { + log.Fatal("DATABASE_URL environment variable is required") + } + + var err error + DB, err = sql.Open("postgres", connectionString) + if err != nil { + log.Fatal(err) + } + + err = DB.Ping() + if err != nil { + log.Fatal(err) + } + + fmt.Println("Successfully connected to database") +} +``` + +## Create Models + +Create your data structures: + +```go +// models/product.go +package models + +import "time" + +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Price float64 `json:"price"` + Rating *int `json:"rating,omitempty"` +} + +type Order struct { + ID int `json:"id"` + CreatedAt time.Time `json:"created_at"` +} + +type OrderItem struct { + OrderID int `json:"order_id"` + ProductID int `json:"product_id"` + Qty int `json:"qty"` +} + +type OrderWithProducts struct { + OrderID int `json:"order_id"` + CreatedAt time.Time `json:"created_at"` + ProductName string `json:"product_name"` + Qty int `json:"qty"` + Total float64 `json:"total"` +} +``` + +## Create Handlers + +Create HTTP handlers for your API: + +```go +// handlers/products.go +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + + "my-xata-app/db" + "my-xata-app/models" + + "github.com/gorilla/mux" +) + +func GetProducts(w http.ResponseWriter, r *http.Request) { + rows, err := db.DB.Query("SELECT id, name, price, rating FROM products") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + var products []models.Product + for rows.Next() { + var p models.Product + err := rows.Scan(&p.ID, &p.Name, &p.Price, &p.Rating) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + products = append(products, p) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(products) +} + +func GetProduct(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + http.Error(w, "Invalid product ID", http.StatusBadRequest) + return + } + + var product models.Product + err = db.DB.QueryRow("SELECT id, name, price, rating FROM products WHERE id = $1", id). + Scan(&product.ID, &product.Name, &product.Price, &product.Rating) + if err != nil { + http.Error(w, "Product not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(product) +} + +func CreateProduct(w http.ResponseWriter, r *http.Request) { + var product models.Product + err := json.NewDecoder(r.Body).Decode(&product) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err = db.DB.QueryRow( + "INSERT INTO products (name, price, rating) VALUES ($1, $2, $3) RETURNING id", + product.Name, product.Price, product.Rating, + ).Scan(&product.ID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(product) +} + +func GetOrders(w http.ResponseWriter, r *http.Request) { + rows, err := db.DB.Query(` + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + oi.qty, + p.price * oi.qty as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + `) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + var orders []models.OrderWithProducts + for rows.Next() { + var order models.OrderWithProducts + err := rows.Scan(&order.OrderID, &order.CreatedAt, &order.ProductName, &order.Qty, &order.Total) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + orders = append(orders, order) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(orders) +} +``` + +## Create Main Application + +Create your main server file: + +```go +// main.go +package main + +import ( + "log" + "net/http" + + "my-xata-app/db" + "my-xata-app/handlers" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" +) + +func main() { + // Load environment variables + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + // Initialize database + db.InitDB() + defer db.DB.Close() + + // Create router + r := mux.NewRouter() + + // Define routes + r.HandleFunc("/api/products", handlers.GetProducts).Methods("GET") + r.HandleFunc("/api/products/{id}", handlers.GetProduct).Methods("GET") + r.HandleFunc("/api/products", handlers.CreateProduct).Methods("POST") + r.HandleFunc("/api/orders", handlers.GetOrders).Methods("GET") + + // Start server + log.Println("Server starting on :3000") + log.Fatal(http.ListenAndServe(":3000", r)) +} +``` + +## Run the Application + +Start your development server: + +```bash +go run main.go +``` + +Your API will be available at: +- `http://localhost:3000/api/products` +- `http://localhost:3000/api/orders` + +## Test Your API + +You can test the endpoints using curl: + +```bash +# Get all products +curl http://localhost:3000/api/products + +# Create a new product +curl -X POST http://localhost:3000/api/products \ + -H "Content-Type: application/json" \ + -d '{"name":"Wireless Headphones","price":99.99,"rating":5}' + +# Get orders with products +curl http://localhost:3000/api/orders +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/gorm.mdx b/docs/quickstarts/gorm.mdx new file mode 100644 index 0000000..ad40729 --- /dev/null +++ b/docs/quickstarts/gorm.mdx @@ -0,0 +1,681 @@ +--- +title: "Connect GORM to Xata" +description: "Learn how to connect your Go application using GORM to Xata's PostgreSQL platform. Get started with GORM and PostgreSQL for robust database operations in Go." +--- + +# Connect GORM to Xata + +Learn how to use GORM with Xata's PostgreSQL platform. GORM is a popular Go ORM library that provides a developer-friendly way to interact with databases, featuring auto-migrations, hooks, and associations. + +## Prerequisites + +- Xata account and project setup +- Go 1.19+ installed +- Basic knowledge of Go and GORM + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Go Project + +Create a new Go project: + +```bash +mkdir my-xata-app +cd my-xata-app +go mod init my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install GORM and PostgreSQL driver: + +```bash +go get -u gorm.io/gorm +go get -u gorm.io/driver/postgres +go get -u github.com/joho/godotenv +``` + +## Create Database Configuration + +Create a database configuration file: + +```go +// config/database.go +package config + +import ( + "fmt" + "log" + "os" + + "github.com/joho/godotenv" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var DB *gorm.DB + +func InitDB() { + // Load environment variables + if err := godotenv.Load(); err != nil { + log.Fatal("Error loading .env file") + } + + dsn := os.Getenv("DATABASE_URL") + if dsn == "" { + log.Fatal("DATABASE_URL environment variable is required") + } + + var err error + DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + + fmt.Println("Database connection established") +} +``` + +## Create Models + +Create your GORM models: + +```go +// models/product.go +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Product struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"type:text;not null" json:"name"` + Price float64 `gorm:"type:decimal(7,2);not null" json:"price"` + Rating *int `gorm:"type:integer" json:"rating"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"` +} + +func (Product) TableName() string { + return "products" +} +``` + +```go +// models/order.go +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Order struct { + ID uint `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + OrderItems []OrderItem `gorm:"foreignKey:OrderID" json:"order_items,omitempty"` +} + +func (Order) TableName() string { + return "orders" +} +``` + +```go +// models/order_item.go +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type OrderItem struct { + OrderID uint `gorm:"primaryKey" json:"order_id"` + ProductID uint `gorm:"primaryKey" json:"product_id"` + Qty int `gorm:"type:integer;not null" json:"qty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + Order Order `gorm:"foreignKey:OrderID" json:"order,omitempty"` + Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"` +} + +func (OrderItem) TableName() string { + return "order_items" +} +``` + +## Create Services + +Create service layer for database operations: + +```go +// services/product_service.go +package services + +import ( + "my-xata-app/config" + "my-xata-app/models" +) + +type ProductService struct{} + +func NewProductService() *ProductService { + return &ProductService{} +} + +func (s *ProductService) GetAllProducts() ([]models.Product, error) { + var products []models.Product + result := config.DB.Find(&products) + return products, result.Error +} + +func (s *ProductService) GetProductByID(id uint) (*models.Product, error) { + var product models.Product + result := config.DB.First(&product, id) + if result.Error != nil { + return nil, result.Error + } + return &product, nil +} + +func (s *ProductService) CreateProduct(product *models.Product) error { + return config.DB.Create(product).Error +} + +func (s *ProductService) UpdateProduct(id uint, updates map[string]interface{}) (*models.Product, error) { + var product models.Product + if err := config.DB.First(&product, id).Error; err != nil { + return nil, err + } + + if err := config.DB.Model(&product).Updates(updates).Error; err != nil { + return nil, err + } + + return &product, nil +} + +func (s *ProductService) DeleteProduct(id uint) error { + return config.DB.Delete(&models.Product{}, id).Error +} + +func (s *ProductService) GetProductsByRating(rating int) ([]models.Product, error) { + var products []models.Product + result := config.DB.Where("rating = ?", rating).Find(&products) + return products, result.Error +} + +func (s *ProductService) GetProductsByPriceRange(minPrice, maxPrice float64) ([]models.Product, error) { + var products []models.Product + result := config.DB.Where("price >= ? AND price <= ?", minPrice, maxPrice).Find(&products) + return products, result.Error +} +``` + +```go +// services/order_service.go +package services + +import ( + "my-xata-app/config" + "my-xata-app/models" +) + +type OrderService struct{} + +func NewOrderService() *OrderService { + return &OrderService{} +} + +func (s *OrderService) GetAllOrders() ([]models.Order, error) { + var orders []models.Order + result := config.DB.Preload("OrderItems.Product").Find(&orders) + return orders, result.Error +} + +func (s *OrderService) GetOrderByID(id uint) (*models.Order, error) { + var order models.Order + result := config.DB.Preload("OrderItems.Product").First(&order, id) + if result.Error != nil { + return nil, result.Error + } + return &order, nil +} + +func (s *OrderService) CreateOrder() (*models.Order, error) { + order := &models.Order{} + result := config.DB.Create(order) + return order, result.Error +} + +func (s *OrderService) AddItemToOrder(orderID, productID uint, qty int) (*models.OrderItem, error) { + orderItem := &models.OrderItem{ + OrderID: orderID, + ProductID: productID, + Qty: qty, + } + result := config.DB.Create(orderItem) + return orderItem, result.Error +} + +func (s *OrderService) GetOrdersWithProductDetails() ([]map[string]interface{}, error) { + var results []map[string]interface{} + + query := ` + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + p.price, + oi.qty, + (p.price * oi.qty) as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + ORDER BY o.created_at DESC + ` + + result := config.DB.Raw(query).Scan(&results) + return results, result.Error +} +``` + +## Create Sample Application + +Create a simple application to demonstrate the functionality: + +```go +// main.go +package main + +import ( + "fmt" + "log" + "my-xata-app/config" + "my-xata-app/models" + "my-xata-app/services" +) + +func main() { + // Initialize database + config.InitDB() + + // Auto-migrate models (optional - since we created tables manually) + if err := config.DB.AutoMigrate(&models.Product{}, &models.Order{}, &models.OrderItem{}); err != nil { + log.Fatal("Failed to migrate database:", err) + } + + // Initialize services + productService := services.NewProductService() + orderService := services.NewOrderService() + + // Create sample products + fmt.Println("Creating sample products...") + products := []*models.Product{ + {Name: "Wireless Headphones", Price: 99.99, Rating: intPtr(5)}, + {Name: "Smartphone", Price: 699.99, Rating: intPtr(4)}, + {Name: "Laptop", Price: 1299.99, Rating: intPtr(5)}, + {Name: "Coffee Maker", Price: 89.99, Rating: intPtr(4)}, + } + + for _, product := range products { + if err := productService.CreateProduct(product); err != nil { + log.Printf("Failed to create product %s: %v", product.Name, err) + } + } + + fmt.Printf("Created %d products\n", len(products)) + + // Display all products + fmt.Println("\nAll products:") + allProducts, err := productService.GetAllProducts() + if err != nil { + log.Printf("Failed to get products: %v", err) + } else { + for _, product := range allProducts { + rating := "N/A" + if product.Rating != nil { + rating = fmt.Sprintf("%d", *product.Rating) + } + fmt.Printf("- %s: $%.2f (Rating: %s/5)\n", product.Name, product.Price, rating) + } + } + + // Get products by rating + fmt.Println("\nTop-rated products (5 stars):") + topProducts, err := productService.GetProductsByRating(5) + if err != nil { + log.Printf("Failed to get top products: %v", err) + } else { + for _, product := range topProducts { + fmt.Printf("- %s: $%.2f\n", product.Name, product.Price) + } + } + + // Create an order with items + fmt.Println("\nCreating an order...") + order, err := orderService.CreateOrder() + if err != nil { + log.Printf("Failed to create order: %v", err) + } else { + fmt.Printf("Created order #%d\n", order.ID) + + // Add items to order + if len(allProducts) >= 3 { + _, err = orderService.AddItemToOrder(order.ID, allProducts[0].ID, 2) // 2 headphones + if err != nil { + log.Printf("Failed to add item to order: %v", err) + } + + _, err = orderService.AddItemToOrder(order.ID, allProducts[2].ID, 1) // 1 laptop + if err != nil { + log.Printf("Failed to add item to order: %v", err) + } + } + } + + // Get order details with products + fmt.Println("\nOrder details:") + orderDetails, err := orderService.GetOrdersWithProductDetails() + if err != nil { + log.Printf("Failed to get order details: %v", err) + } else { + for _, detail := range orderDetails { + fmt.Printf("Order #%.0f: %s x%.0f = $%.2f\n", + detail["order_id"], detail["product_name"], detail["qty"], detail["total"]) + } + } + + // Update a product + if len(allProducts) > 0 { + fmt.Println("\nUpdating product...") + updates := map[string]interface{}{ + "name": "Premium Wireless Headphones", + "price": 129.99, + } + updatedProduct, err := productService.UpdateProduct(allProducts[0].ID, updates) + if err != nil { + log.Printf("Failed to update product: %v", err) + } else { + fmt.Printf("Updated: %s - $%.2f\n", updatedProduct.Name, updatedProduct.Price) + } + } +} + +func intPtr(i int) *int { + return &i +} +``` + +## Create Go Modules + +Create a `go.mod` file: + +```go +// go.mod +module my-xata-app + +go 1.19 + +require ( + github.com/joho/godotenv v1.5.1 + gorm.io/driver/postgres v1.5.4 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect +) +``` + +## Run the Application + +Run the application: + +```bash +go run main.go +``` + +## Create HTTP API (Optional) + +For a web API, you can integrate with Gin: + +```go +// api/main.go +package main + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "my-xata-app/config" + "my-xata-app/models" + "my-xata-app/services" +) + +func main() { + // Initialize database + config.InitDB() + + // Auto-migrate models + if err := config.DB.AutoMigrate(&models.Product{}, &models.Order{}, &models.OrderItem{}); err != nil { + panic("Failed to migrate database") + } + + // Initialize services + productService := services.NewProductService() + orderService := services.NewOrderService() + + // Setup Gin router + r := gin.Default() + + // Product routes + r.GET("/api/products", func(c *gin.Context) { + products, err := productService.GetAllProducts() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch products"}) + return + } + c.JSON(http.StatusOK, products) + }) + + r.GET("/api/products/:id", func(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + product, err := productService.GetProductByID(uint(id)) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + c.JSON(http.StatusOK, product) + }) + + r.POST("/api/products", func(c *gin.Context) { + var product models.Product + if err := c.ShouldBindJSON(&product); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + return + } + + if err := productService.CreateProduct(&product); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"}) + return + } + c.JSON(http.StatusCreated, product) + }) + + r.PUT("/api/products/:id", func(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + var updates map[string]interface{} + if err := c.ShouldBindJSON(&updates); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) + return + } + + product, err := productService.UpdateProduct(uint(id), updates) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + c.JSON(http.StatusOK, product) + }) + + r.DELETE("/api/products/:id", func(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + if err := productService.DeleteProduct(uint(id)); err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + c.Status(http.StatusNoContent) + }) + + r.GET("/api/products/rating/:rating", func(c *gin.Context) { + rating, err := strconv.Atoi(c.Param("rating")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid rating"}) + return + } + + products, err := productService.GetProductsByRating(rating) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch products"}) + return + } + c.JSON(http.StatusOK, products) + }) + + // Order routes + r.GET("/api/orders", func(c *gin.Context) { + orders, err := orderService.GetAllOrders() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch orders"}) + return + } + c.JSON(http.StatusOK, orders) + }) + + r.GET("/api/orders/:id", func(c *gin.Context) { + id, err := strconv.ParseUint(c.Param("id"), 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) + return + } + + order, err := orderService.GetOrderByID(uint(id)) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + c.JSON(http.StatusOK, order) + }) + + // Start server + r.Run(":8080") +} +``` + +To run the HTTP API: + +```bash +go get github.com/gin-gonic/gin +go run api/main.go +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/java.mdx b/docs/quickstarts/java.mdx new file mode 100644 index 0000000..694c930 --- /dev/null +++ b/docs/quickstarts/java.mdx @@ -0,0 +1,495 @@ +--- +title: "Connect Java Spring Boot to Xata" +description: "Learn how to connect your Java Spring Boot application to Xata's PostgreSQL platform. Get started with Spring Boot and PostgreSQL for enterprise applications." +--- + +# Connect Java Spring Boot to Xata + +Learn how to use Java Spring Boot with Xata's PostgreSQL platform. Spring Boot is a powerful Java framework that simplifies the development of production-ready applications with built-in database support. + +## Prerequisites + +- Xata account and project setup +- Java 17+ installed +- Maven or Gradle installed +- Basic knowledge of Java and Spring Boot + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Spring Boot Project + +Create a new Spring Boot project using Spring Initializr: + +```bash +curl https://start.spring.io/starter.tgz \ + -d type=maven-project \ + -d language=java \ + -d bootVersion=3.2.0 \ + -d baseDir=my-xata-app \ + -d groupId=com.example \ + -d artifactId=my-xata-app \ + -d name=my-xata-app \ + -d description="Xata Spring Boot Application" \ + -d packageName=com.example.myxataapp \ + -d packaging=jar \ + -d javaVersion=17 \ + -d dependencies=web,data-jpa,postgresql,validation \ + | tar -xzvf - +cd my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create an `application.properties` file to store your Xata connection details: + +```properties +# src/main/resources/application.properties +spring.datasource.url=jdbc:postgresql://your_host:5432/your_database_name +spring.datasource.username=your_username +spring.datasource.password=your_password +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.jpa.hibernate.ddl-auto=none +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.hibernate.format_sql=true + +server.port=8080 +``` + +Get your connection details from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your database credentials to version control. Use environment variables or external configuration for production. + +## Create Entity Models + +Create your JPA entities: + +```java +// src/main/java/com/example/myxataapp/entity/Product.java +package com.example.myxataapp.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.List; + +@Entity +@Table(name = "products") +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank + @Column(name = "name", nullable = false) + private String name; + + @NotNull + @DecimalMin("0.0") + @Column(name = "price", nullable = false, precision = 7, scale = 2) + private BigDecimal price; + + @Column(name = "rating") + private Integer rating; + + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL) + private List orderItems; + + // Constructors + public Product() {} + + public Product(String name, BigDecimal price, Integer rating) { + this.name = name; + this.price = price; + this.rating = rating; + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public BigDecimal getPrice() { return price; } + public void setPrice(BigDecimal price) { this.price = price; } + + public Integer getRating() { return rating; } + public void setRating(Integer rating) { this.rating = rating; } + + public List getOrderItems() { return orderItems; } + public void setOrderItems(List orderItems) { this.orderItems = orderItems; } +} +``` + +```java +// src/main/java/com/example/myxataapp/entity/Order.java +package com.example.myxataapp.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) + private List orderItems; + + // Constructors + public Order() { + this.createdAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public List getOrderItems() { return orderItems; } + public void setOrderItems(List orderItems) { this.orderItems = orderItems; } +} +``` + +```java +// src/main/java/com/example/myxataapp/entity/OrderItem.java +package com.example.myxataapp.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +@Entity +@Table(name = "order_items") +@IdClass(OrderItemId.class) +public class OrderItem { + @Id + @Column(name = "order_id") + private Long orderId; + + @Id + @Column(name = "product_id") + private Long productId; + + @NotNull + @Min(1) + @Column(name = "qty", nullable = false) + private Integer qty; + + @ManyToOne + @JoinColumn(name = "order_id", insertable = false, updatable = false) + private Order order; + + @ManyToOne + @JoinColumn(name = "product_id", insertable = false, updatable = false) + private Product product; + + // Constructors + public OrderItem() {} + + public OrderItem(Long orderId, Long productId, Integer qty) { + this.orderId = orderId; + this.productId = productId; + this.qty = qty; + } + + // Getters and Setters + public Long getOrderId() { return orderId; } + public void setOrderId(Long orderId) { this.orderId = orderId; } + + public Long getProductId() { return productId; } + public void setProductId(Long productId) { this.productId = productId; } + + public Integer getQty() { return qty; } + public void setQty(Integer qty) { this.qty = qty; } + + public Order getOrder() { return order; } + public void setOrder(Order order) { this.order = order; } + + public Product getProduct() { return product; } + public void setProduct(Product product) { this.product = product; } +} +``` + +```java +// src/main/java/com/example/myxataapp/entity/OrderItemId.java +package com.example.myxataapp.entity; + +import java.io.Serializable; +import java.util.Objects; + +public class OrderItemId implements Serializable { + private Long orderId; + private Long productId; + + public OrderItemId() {} + + public OrderItemId(Long orderId, Long productId) { + this.orderId = orderId; + this.productId = productId; + } + + public Long getOrderId() { return orderId; } + public void setOrderId(Long orderId) { this.orderId = orderId; } + + public Long getProductId() { return productId; } + public void setProductId(Long productId) { this.productId = productId; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderItemId that = (OrderItemId) o; + return Objects.equals(orderId, that.orderId) && Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + return Objects.hash(orderId, productId); + } +} +``` + +## Create Repositories + +Create Spring Data JPA repositories: + +```java +// src/main/java/com/example/myxataapp/repository/ProductRepository.java +package com.example.myxataapp.repository; + +import com.example.myxataapp.entity.Product; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface ProductRepository extends JpaRepository { + List findByRating(Integer rating); + + @Query("SELECT p FROM Product p WHERE p.price >= ?1") + List findByPriceGreaterThanEqual(java.math.BigDecimal price); +} +``` + +```java +// src/main/java/com/example/myxataapp/repository/OrderRepository.java +package com.example.myxataapp.repository; + +import com.example.myxataapp.entity.Order; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository +public interface OrderRepository extends JpaRepository { + @Query("SELECT o FROM Order o JOIN FETCH o.orderItems oi JOIN FETCH oi.product") + List findAllWithProducts(); +} +``` + +## Create Services + +Create service layer for business logic: + +```java +// src/main/java/com/example/myxataapp/service/ProductService.java +package com.example.myxataapp.service; + +import com.example.myxataapp.entity.Product; +import com.example.myxataapp.repository.ProductRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + +@Service +public class ProductService { + @Autowired + private ProductRepository productRepository; + + public List getAllProducts() { + return productRepository.findAll(); + } + + public Optional getProductById(Long id) { + return productRepository.findById(id); + } + + public Product createProduct(Product product) { + return productRepository.save(product); + } + + public Product updateProduct(Long id, Product productDetails) { + Product product = productRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Product not found")); + + product.setName(productDetails.getName()); + product.setPrice(productDetails.getPrice()); + product.setRating(productDetails.getRating()); + + return productRepository.save(product); + } + + public void deleteProduct(Long id) { + productRepository.deleteById(id); + } + + public List getProductsByRating(Integer rating) { + return productRepository.findByRating(rating); + } +} +``` + +## Create Controllers + +Create REST controllers: + +```java +// src/main/java/com/example/myxataapp/controller/ProductController.java +package com.example.myxataapp.controller; + +import com.example.myxataapp.entity.Product; +import com.example.myxataapp.service.ProductService; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RestController +@RequestMapping("/api/products") +@CrossOrigin(origins = "*") +public class ProductController { + @Autowired + private ProductService productService; + + @GetMapping + public ResponseEntity> getAllProducts() { + List products = productService.getAllProducts(); + return ResponseEntity.ok(products); + } + + @GetMapping("/{id}") + public ResponseEntity getProductById(@PathVariable Long id) { + return productService.getProductById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PostMapping + public ResponseEntity createProduct(@Valid @RequestBody Product product) { + Product createdProduct = productService.createProduct(product); + return ResponseEntity.status(HttpStatus.CREATED).body(createdProduct); + } + + @PutMapping("/{id}") + public ResponseEntity updateProduct(@PathVariable Long id, @Valid @RequestBody Product product) { + try { + Product updatedProduct = productService.updateProduct(id, product); + return ResponseEntity.ok(updatedProduct); + } catch (RuntimeException e) { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteProduct(@PathVariable Long id) { + productService.deleteProduct(id); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/rating/{rating}") + public ResponseEntity> getProductsByRating(@PathVariable Integer rating) { + List products = productService.getProductsByRating(rating); + return ResponseEntity.ok(products); + } +} +``` + +## Run the Application + +Start your Spring Boot application: + +```bash +./mvnw spring-boot:run +``` + +Visit `http://localhost:8080` to see your application. + +Test the API endpoints: + +```bash +# Get all products +curl http://localhost:8080/api/products + +# Create a product +curl -X POST http://localhost:8080/api/products \ + -H "Content-Type: application/json" \ + -d '{"name":"New Product","price":49.99,"rating":4}' + +# Get products by rating +curl http://localhost:8080/api/products/rating/5 +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/jpa.mdx b/docs/quickstarts/jpa.mdx new file mode 100644 index 0000000..6effb49 --- /dev/null +++ b/docs/quickstarts/jpa.mdx @@ -0,0 +1,722 @@ +--- +title: "Connect JPA/Hibernate to Xata" +description: "Learn how to connect your Java application using JPA/Hibernate to Xata's PostgreSQL platform. Get started with JPA and PostgreSQL for enterprise-grade database operations." +--- + +# Connect JPA/Hibernate to Xata + +Learn how to use JPA (Java Persistence API) with Hibernate and Xata's PostgreSQL platform. JPA is the standard Java specification for object-relational mapping, and Hibernate is the most popular implementation, providing powerful database abstraction and persistence capabilities. + +## Prerequisites + +- Xata account and project setup +- Java 17+ installed +- Maven or Gradle installed +- Basic knowledge of Java and JPA/Hibernate + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Maven Project + +Create a new Maven project with the following structure: + +```bash +mkdir my-xata-app +cd my-xata-app +``` + +Create a `pom.xml` file: + +```xml + + + 4.0.0 + + com.example + my-xata-app + 1.0-SNAPSHOT + jar + + Xata JPA Application + JPA/Hibernate application with Xata PostgreSQL + + + 17 + 17 + UTF-8 + 6.2.13.Final + + + + + + org.hibernate.orm + hibernate-core + ${hibernate.version} + + + + + org.postgresql + postgresql + 42.7.1 + + + + + io.github.cdimascio + dotenv-java + 3.0.0 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + + + org.slf4j + slf4j-simple + 2.0.9 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL=postgresql://username:password@host:port/database +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Create JPA Configuration + +Create the JPA configuration file: + +```xml + + + + + + org.hibernate.jpa.HibernatePersistenceProvider + + com.example.entity.Product + com.example.entity.Order + com.example.entity.OrderItem + + + + + + + + + + + + + + + + + + + + +``` + +## Create Entity Classes + +Create your JPA entity classes: + +```java +// src/main/java/com/example/entity/Product.java +package com.example.entity; + +import jakarta.persistence.*; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "products") +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "price", nullable = false, precision = 7, scale = 2) + private BigDecimal price; + + @Column(name = "rating") + private Integer rating; + + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List orderItems = new ArrayList<>(); + + // Constructors + public Product() {} + + public Product(String name, BigDecimal price, Integer rating) { + this.name = name; + this.price = price; + this.rating = rating; + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public BigDecimal getPrice() { return price; } + public void setPrice(BigDecimal price) { this.price = price; } + + public Integer getRating() { return rating; } + public void setRating(Integer rating) { this.rating = rating; } + + public List getOrderItems() { return orderItems; } + public void setOrderItems(List orderItems) { this.orderItems = orderItems; } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + ", rating=" + rating + + '}'; + } +} +``` + +```java +// src/main/java/com/example/entity/Order.java +package com.example.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private List orderItems = new ArrayList<>(); + + // Constructors + public Order() { + this.createdAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public List getOrderItems() { return orderItems; } + public void setOrderItems(List orderItems) { this.orderItems = orderItems; } + + @Override + public String toString() { + return "Order{" + + "id=" + id + + ", createdAt=" + createdAt + + '}'; + } +} +``` + +```java +// src/main/java/com/example/entity/OrderItem.java +package com.example.entity; + +import jakarta.persistence.*; +import java.io.Serializable; + +@Entity +@Table(name = "order_items") +@IdClass(OrderItem.OrderItemId.class) +public class OrderItem { + @Id + @Column(name = "order_id") + private Long orderId; + + @Id + @Column(name = "product_id") + private Long productId; + + @Column(name = "qty", nullable = false) + private Integer qty; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "order_id", insertable = false, updatable = false) + private Order order; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id", insertable = false, updatable = false) + private Product product; + + // Constructors + public OrderItem() {} + + public OrderItem(Long orderId, Long productId, Integer qty) { + this.orderId = orderId; + this.productId = productId; + this.qty = qty; + } + + // Getters and Setters + public Long getOrderId() { return orderId; } + public void setOrderId(Long orderId) { this.orderId = orderId; } + + public Long getProductId() { return productId; } + public void setProductId(Long productId) { this.productId = productId; } + + public Integer getQty() { return qty; } + public void setQty(Integer qty) { this.qty = qty; } + + public Order getOrder() { return order; } + public void setOrder(Order order) { this.order = order; } + + public Product getProduct() { return product; } + public void setProduct(Product product) { this.product = product; } + + // Composite Key Class + public static class OrderItemId implements Serializable { + private Long orderId; + private Long productId; + + public OrderItemId() {} + + public OrderItemId(Long orderId, Long productId) { + this.orderId = orderId; + this.productId = productId; + } + + public Long getOrderId() { return orderId; } + public void setOrderId(Long orderId) { this.orderId = orderId; } + + public Long getProductId() { return productId; } + public void setProductId(Long productId) { this.productId = productId; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderItemId that = (OrderItemId) o; + return orderId.equals(that.orderId) && productId.equals(that.productId); + } + + @Override + public int hashCode() { + return orderId.hashCode() * 31 + productId.hashCode(); + } + } + + @Override + public String toString() { + return "OrderItem{" + + "orderId=" + orderId + + ", productId=" + productId + + ", qty=" + qty + + '}'; + } +} +``` + +## Create Database Utility + +Create a database utility class: + +```java +// src/main/java/com/example/util/DatabaseUtil.java +package com.example.util; + +import io.github.cdimascio.dotenv.Dotenv; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Persistence; + +public class DatabaseUtil { + private static EntityManagerFactory emf; + private static final String PERSISTENCE_UNIT_NAME = "xataPU"; + + static { + // Load environment variables + Dotenv dotenv = Dotenv.load(); + + // Set system properties for JPA configuration + String databaseUrl = dotenv.get("DATABASE_URL"); + if (databaseUrl != null) { + // Parse the database URL to extract components + String[] parts = databaseUrl.replace("postgresql://", "").split("@"); + String[] credentials = parts[0].split(":"); + String[] hostAndPort = parts[1].split("/"); + String[] hostPort = hostAndPort[0].split(":"); + + String username = credentials[0]; + String password = credentials[1]; + String host = hostPort[0]; + String port = hostPort.length > 1 ? hostPort[1] : "5432"; + String database = hostAndPort[1]; + + System.setProperty("DB_USERNAME", username); + System.setProperty("DB_PASSWORD", password); + System.setProperty("DATABASE_URL", "jdbc:postgresql://" + host + ":" + port + "/" + database); + } + } + + public static EntityManagerFactory getEntityManagerFactory() { + if (emf == null || !emf.isOpen()) { + emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME); + } + return emf; + } + + public static EntityManager getEntityManager() { + return getEntityManagerFactory().createEntityManager(); + } + + public static void closeEntityManagerFactory() { + if (emf != null && emf.isOpen()) { + emf.close(); + } + } +} +``` + +## Create Service Classes + +Create service classes for database operations: + +```java +// src/main/java/com/example/service/ProductService.java +package com.example.service; + +import com.example.entity.Product; +import com.example.util.DatabaseUtil; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +public class ProductService { + + public List getAllProducts() { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + TypedQuery query = em.createQuery("SELECT p FROM Product p", Product.class); + return query.getResultList(); + } finally { + em.close(); + } + } + + public Optional getProductById(Long id) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + Product product = em.find(Product.class, id); + return Optional.ofNullable(product); + } finally { + em.close(); + } + } + + public Product createProduct(String name, BigDecimal price, Integer rating) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + em.getTransaction().begin(); + + Product product = new Product(name, price, rating); + em.persist(product); + + em.getTransaction().commit(); + return product; + } catch (Exception e) { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + throw e; + } finally { + em.close(); + } + } + + public Optional updateProduct(Long id, String name, BigDecimal price, Integer rating) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + em.getTransaction().begin(); + + Product product = em.find(Product.class, id); + if (product == null) { + em.getTransaction().rollback(); + return Optional.empty(); + } + + if (name != null) product.setName(name); + if (price != null) product.setPrice(price); + if (rating != null) product.setRating(rating); + + em.getTransaction().commit(); + return Optional.of(product); + } catch (Exception e) { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + throw e; + } finally { + em.close(); + } + } + + public boolean deleteProduct(Long id) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + em.getTransaction().begin(); + + Product product = em.find(Product.class, id); + if (product == null) { + em.getTransaction().rollback(); + return false; + } + + em.remove(product); + em.getTransaction().commit(); + return true; + } catch (Exception e) { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + throw e; + } finally { + em.close(); + } + } + + public List getProductsByRating(Integer rating) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + TypedQuery query = em.createQuery( + "SELECT p FROM Product p WHERE p.rating = :rating", Product.class); + query.setParameter("rating", rating); + return query.getResultList(); + } finally { + em.close(); + } + } + + public List getProductsByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) { + EntityManager em = DatabaseUtil.getEntityManager(); + try { + TypedQuery query = em.createQuery( + "SELECT p FROM Product p WHERE p.price >= :minPrice AND p.price <= :maxPrice", + Product.class); + query.setParameter("minPrice", minPrice); + query.setParameter("maxPrice", maxPrice); + return query.getResultList(); + } finally { + em.close(); + } + } +} +``` + +## Create Main Application + +Create the main application class: + +```java +// src/main/java/com/example/Main.java +package com.example; + +import com.example.entity.Product; +import com.example.service.OrderService; +import com.example.service.ProductService; +import com.example.util.DatabaseUtil; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +public class Main { + public static void main(String[] args) { + try { + ProductService productService = new ProductService(); + OrderService orderService = new OrderService(); + + // Create sample products + System.out.println("Creating sample products..."); + List products = List.of( + productService.createProduct("Wireless Headphones", new BigDecimal("99.99"), 5), + productService.createProduct("Smartphone", new BigDecimal("699.99"), 4), + productService.createProduct("Laptop", new BigDecimal("1299.99"), 5), + productService.createProduct("Coffee Maker", new BigDecimal("89.99"), 4) + ); + + System.out.println("Created " + products.size() + " products"); + + // Display all products + System.out.println("\nAll products:"); + List allProducts = productService.getAllProducts(); + for (Product product : allProducts) { + String rating = product.getRating() != null ? product.getRating().toString() : "N/A"; + System.out.printf("- %s: $%s (Rating: %s/5)%n", + product.getName(), product.getPrice(), rating); + } + + // Get products by rating + System.out.println("\nTop-rated products (5 stars):"); + List topProducts = productService.getProductsByRating(5); + for (Product product : topProducts) { + System.out.printf("- %s: $%s%n", product.getName(), product.getPrice()); + } + + // Create an order with items + System.out.println("\nCreating an order..."); + var order = orderService.createOrder(); + System.out.println("Created order #" + order.getId()); + + // Add items to order + if (!products.isEmpty()) { + orderService.addItemToOrder(order.getId(), products.get(0).getId(), 2); // 2 headphones + if (products.size() >= 3) { + orderService.addItemToOrder(order.getId(), products.get(2).getId(), 1); // 1 laptop + } + } + + // Get order details with products + System.out.println("\nOrder details:"); + List> orderDetails = orderService.getOrdersWithProductDetails(); + for (Map detail : orderDetails) { + System.out.printf("Order #%s: %s x%s = $%s%n", + detail.get("order_id"), detail.get("product_name"), + detail.get("qty"), detail.get("total")); + } + + // Update a product + if (!products.isEmpty()) { + System.out.println("\nUpdating product..."); + var updatedProduct = productService.updateProduct( + products.get(0).getId(), + "Premium Wireless Headphones", + new BigDecimal("129.99"), + null + ); + if (updatedProduct.isPresent()) { + Product product = updatedProduct.get(); + System.out.printf("Updated: %s - $%s%n", product.getName(), product.getPrice()); + } + } + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } finally { + DatabaseUtil.closeEntityManagerFactory(); + } + } +} +``` + +## Run the Application + +Compile and run the application: + +```bash +mvn compile +mvn exec:java -Dexec.mainClass="com.example.Main" +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/laravel.mdx b/docs/quickstarts/laravel.mdx new file mode 100644 index 0000000..22a53dd --- /dev/null +++ b/docs/quickstarts/laravel.mdx @@ -0,0 +1,406 @@ +--- +title: "Connect Laravel to Xata" +description: "Learn how to connect your Laravel application to Xata's PostgreSQL platform. Get started with Laravel and PostgreSQL for robust web applications." +--- + +# Connect Laravel to Xata + +Learn how to use Laravel with Xata's PostgreSQL platform. Laravel is a powerful PHP web framework with an elegant ORM (Eloquent) that makes database operations intuitive and efficient. + +## Prerequisites + +- Xata account and project setup +- PHP 8.1+ installed +- Composer installed +- Basic knowledge of PHP and Laravel + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Laravel Project + +Create a new Laravel project: + +```bash +composer create-project laravel/laravel my-xata-app +cd my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DB_CONNECTION=pgsql +DB_HOST=your_host +DB_PORT=5432 +DB_DATABASE=your_database_name +DB_USERNAME=your_username +DB_PASSWORD=your_password +``` + +Get your connection details from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL driver for PHP: + +```bash +composer require pgsql +``` + +## Configure Database Connection + +Update your `config/database.php` to use PostgreSQL: + +```php +// config/database.php +'default' => env('DB_CONNECTION', 'pgsql'), + +'connections' => [ + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'schema' => 'public', + 'sslmode' => 'prefer', + ], +], +``` + +## Create Models + +Generate your Eloquent models: + +```bash +php artisan make:model Product +php artisan make:model Order +php artisan make:model OrderItem +``` + +Update the models with proper relationships: + +```php +// app/Models/Product.php + 'decimal:2', + 'rating' => 'integer', + ]; + + public function orderItems(): HasMany + { + return $this->hasMany(OrderItem::class); + } + + public function orders() + { + return $this->belongsToMany(Order::class, 'order_items') + ->withPivot('qty') + ->withTimestamps(); + } +} +``` + +```php +// app/Models/Order.php + 'datetime', + ]; + + public function orderItems(): HasMany + { + return $this->hasMany(OrderItem::class); + } + + public function products() + { + return $this->belongsToMany(Product::class, 'order_items') + ->withPivot('qty') + ->withTimestamps(); + } +} +``` + +```php +// app/Models/OrderItem.php + 'integer', + ]; + + public function order(): BelongsTo + { + return $this->belongsTo(Order::class); + } + + public function product(): BelongsTo + { + return $this->belongsTo(Product::class); + } +} +``` + +## Create Controllers + +Generate API controllers: + +```bash +php artisan make:controller Api/ProductController --api +php artisan make:controller Api/OrderController --api +``` + +Update the controllers: + +```php +// app/Http/Controllers/Api/ProductController.php +json($products); + } + + public function show(Product $product): JsonResponse + { + return response()->json($product); + } + + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'price' => 'required|numeric|min:0', + 'rating' => 'nullable|integer|min:1|max:5', + ]); + + $product = Product::create($validated); + return response()->json($product, 201); + } + + public function update(Request $request, Product $product): JsonResponse + { + $validated = $request->validate([ + 'name' => 'sometimes|string|max:255', + 'price' => 'sometimes|numeric|min:0', + 'rating' => 'nullable|integer|min:1|max:5', + ]); + + $product->update($validated); + return response()->json($product); + } + + public function destroy(Product $product): JsonResponse + { + $product->delete(); + return response()->json(null, 204); + } +} +``` + +```php +// app/Http/Controllers/Api/OrderController.php + function ($query) { + $query->select('products.id', 'name', 'price') + ->withPivot('qty'); + }])->get(); + + return response()->json($orders); + } + + public function show(Order $order): JsonResponse + { + $order->load(['products' => function ($query) { + $query->select('products.id', 'name', 'price') + ->withPivot('qty'); + }]); + + return response()->json($order); + } +} +``` + +## Define Routes + +Add API routes in `routes/api.php`: + +```php +// routes/api.php +only(['index', 'show']); +``` + +## Create Seeders + +Generate seeders for sample data: + +```bash +php artisan make:seeder ProductSeeder +php artisan make:seeder OrderSeeder +``` + +```php +// database/seeders/ProductSeeder.php + 'Wireless Headphones', 'price' => 99.99, 'rating' => 5], + ['name' => 'Smartphone', 'price' => 699.99, 'rating' => 4], + ['name' => 'Laptop', 'price' => 1299.99, 'rating' => 5], + ['name' => 'Coffee Maker', 'price' => 89.99, 'rating' => 4], + ]; + + foreach ($products as $product) { + Product::create($product); + } + } +} +``` + +## Run the Application + +Start your development server: + +```bash +php artisan serve +``` + +Visit `http://localhost:8000` to see your application. + +Test the API endpoints: + +```bash +# Get all products +curl http://localhost:8000/api/products + +# Create a product +curl -X POST http://localhost:8000/api/products \ + -H "Content-Type: application/json" \ + -d '{"name":"New Product","price":49.99,"rating":4}' + +# Get all orders +curl http://localhost:8000/api/orders +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/nextjs.mdx b/docs/quickstarts/nextjs.mdx new file mode 100644 index 0000000..aca2ebf --- /dev/null +++ b/docs/quickstarts/nextjs.mdx @@ -0,0 +1,246 @@ +--- +title: "Connect Next.js to Xata" +description: "Learn how to connect your Next.js application to Xata's PostgreSQL platform. Get started with Next.js and PostgreSQL for scalable web applications." +--- + +# Connect Next.js to Xata + +Learn how to use Next.js with Xata's PostgreSQL platform. Next.js provides excellent React framework features with server-side rendering and API routes. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of React and Next.js + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Next.js Project + +Create a new Next.js project with TypeScript: + +```bash +npx create-next-app@latest my-xata-app --typescript --tailwind --eslint +cd my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env.local` file in your project root to store your Xata connection string: + +```bash +# .env.local +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env.local` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL client: + +```bash +npm install postgres +npm install -D @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// lib/db.ts +import postgres from 'postgres'; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create API Routes + +Create API routes for your products: + +```typescript +// app/api/products/route.ts +import { NextResponse } from 'next/server'; +import { sql } from '@/lib/db'; + +export async function GET() { + try { + const products = await sql`SELECT * FROM products`; + return NextResponse.json(products); + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch products' }, { status: 500 }); + } +} + +export async function POST(request: Request) { + try { + const body = await request.json(); + const [newProduct] = await sql` + INSERT INTO products (name, price, rating) + VALUES (${body.name}, ${body.price}, ${body.rating}) + RETURNING * + `; + return NextResponse.json(newProduct, { status: 201 }); + } catch (error) { + return NextResponse.json({ error: 'Failed to create product' }, { status: 500 }); + } +} +``` + +```typescript +// app/api/products/[id]/route.ts +import { NextResponse } from 'next/server'; +import { sql } from '@/lib/db'; + +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { + try { + const [product] = await sql`SELECT * FROM products WHERE id = ${params.id}`; + + if (!product) { + return NextResponse.json({ error: 'Product not found' }, { status: 404 }); + } + + return NextResponse.json(product); + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch product' }, { status: 500 }); + } +} +``` + +## Create Components + +Create a products list component: + +```typescript +// components/ProductsList.tsx +'use client'; + +import { useState, useEffect } from 'react'; +import type { Product } from '@/lib/schema'; + +export default function ProductsList() { + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch('/api/products') + .then(res => res.json()) + .then(data => { + setProducts(data); + setLoading(false); + }) + .catch(error => { + console.error('Error fetching products:', error); + setLoading(false); + }); + }, []); + + if (loading) return
Loading products...
; + + return ( +
+ {products.map((product) => ( +
+

{product.name}

+

${product.price}

+ {product.rating && ( +
+ + {product.rating}/5 +
+ )} +
+ ))} +
+ ); +} +``` + +## Update Main Page + +Update your main page to display products: + +```typescript +// app/page.tsx +import ProductsList from '@/components/ProductsList'; + +export default function Home() { + return ( +
+

Xata E-commerce Store

+ +
+ ); +} +``` + + + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Visit `http://localhost:3000` to see your application. + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/nodejs.mdx b/docs/quickstarts/nodejs.mdx new file mode 100644 index 0000000..6faf529 --- /dev/null +++ b/docs/quickstarts/nodejs.mdx @@ -0,0 +1,258 @@ +--- +title: "Connect Node.js to Xata" +description: "Learn how to connect your Node.js application to Xata's PostgreSQL platform. Get started with Node.js and PostgreSQL for scalable backend services." +--- + +# Connect Node.js to Xata + +Learn how to use Node.js with Xata's PostgreSQL platform. Node.js provides excellent JavaScript runtime for building fast, scalable backend services and APIs. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of Node.js and Express + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Node.js Project + +Create a new Node.js project: + +```bash +mkdir my-xata-app +cd my-xata-app +npm init -y +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install Express and the PostgreSQL client: + +```bash +npm install express postgres cors dotenv +npm install -D @types/pg @types/express @types/cors nodemon +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// db.ts +import postgres from 'postgres'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create Express Server + +Create your main server file: + +```typescript +// server.ts +import express from 'express'; +import cors from 'cors'; +import { sql } from './db'; + +const app = express(); +const PORT = process.env.PORT || 3000; + +app.use(cors()); +app.use(express.json()); + +// Get all products +app.get('/api/products', async (req, res) => { + try { + const products = await sql`SELECT * FROM products`; + res.json(products); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch products' }); + } +}); + +// Get product by ID +app.get('/api/products/:id', async (req, res) => { + try { + const [product] = await sql`SELECT * FROM products WHERE id = ${req.params.id}`; + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + res.json(product); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch product' }); + } +}); + +// Create new product +app.post('/api/products', async (req, res) => { + try { + const { name, price, rating } = req.body; + const [newProduct] = await sql` + INSERT INTO products (name, price, rating) + VALUES (${name}, ${price}, ${rating}) + RETURNING * + `; + res.status(201).json(newProduct); + } catch (error) { + res.status(500).json({ error: 'Failed to create product' }); + } +}); + +// Get orders with products +app.get('/api/orders', async (req, res) => { + try { + const orders = await sql` + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + oi.qty, + p.price * oi.qty as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + `; + res.json(orders); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch orders' }); + } +}); + +app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); +}); +``` + +## Add Scripts + +Update your `package.json` to include development scripts: + +```json +{ + "scripts": { + "dev": "nodemon server.ts", + "start": "node server.ts", + "build": "tsc" + } +} +``` + +## Create TypeScript Config + +Create a `tsconfig.json` file: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} +``` + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Your API will be available at `http://localhost:3000/api/products`. + +## Test Your API + +You can test the endpoints using curl or a tool like Postman: + +```bash +# Get all products +curl http://localhost:3000/api/products + +# Create a new product +curl -X POST http://localhost:3000/api/products \ + -H "Content-Type: application/json" \ + -d '{"name":"Wireless Headphones","price":99.99,"rating":5}' + +# Get orders with products +curl http://localhost:3000/api/orders +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/nuxt.mdx b/docs/quickstarts/nuxt.mdx new file mode 100644 index 0000000..384e866 --- /dev/null +++ b/docs/quickstarts/nuxt.mdx @@ -0,0 +1,264 @@ +--- +title: "Connect Nuxt to Xata" +description: "Learn how to connect your Nuxt application to Xata's PostgreSQL platform. Get started with Nuxt and PostgreSQL for universal Vue.js applications." +--- + +# Connect Nuxt to Xata + +Learn how to use Nuxt with Xata's PostgreSQL platform. Nuxt provides excellent Vue.js framework features with server-side rendering and API routes. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of Vue.js and Nuxt + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Nuxt Project + +Create a new Nuxt project: + +```bash +npx nuxi@latest init my-xata-app +cd my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL client: + +```bash +npm install postgres +npm install -D @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// server/utils/db.ts +import postgres from 'postgres'; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create Server API Routes + +Create API routes for your products: + +```typescript +// server/api/products.get.ts +import { sql } from '~/server/utils/db'; + +export default defineEventHandler(async (event) => { + try { + const products = await sql`SELECT * FROM products`; + return products; + } catch (error) { + throw createError({ + statusCode: 500, + statusMessage: 'Failed to fetch products' + }); + } +}); +``` + +```typescript +// server/api/products.post.ts +import { sql } from '~/server/utils/db'; + +export default defineEventHandler(async (event) => { + try { + const body = await readBody(event); + const [newProduct] = await sql` + INSERT INTO products (name, price, rating) + VALUES (${body.name}, ${body.price}, ${body.rating}) + RETURNING * + `; + return newProduct; + } catch (error) { + throw createError({ + statusCode: 500, + statusMessage: 'Failed to create product' + }); + } +}); +``` + +```typescript +// server/api/products/[id].get.ts +import { sql } from '~/server/utils/db'; + +export default defineEventHandler(async (event) => { + try { + const id = getRouterParam(event, 'id'); + const [product] = await sql`SELECT * FROM products WHERE id = ${id}`; + + if (!product) { + throw createError({ + statusCode: 404, + statusMessage: 'Product not found' + }); + } + + return product; + } catch (error) { + throw createError({ + statusCode: 500, + statusMessage: 'Failed to fetch product' + }); + } +}); +``` + +## Create Components + +Create a products list component: + +```vue + + + + +``` + +## Create Pages + +Create the main page to display products: + +```vue + + + + +``` + +## Configure Nuxt + +Update your `nuxt.config.ts` to include TypeScript support: + +```typescript +// nuxt.config.ts +export default defineNuxtConfig({ + devtools: { enabled: true }, + typescript: { + strict: true + } +}); +``` + + + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Visit `http://localhost:3000` to see your application. + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/prisma.mdx b/docs/quickstarts/prisma.mdx new file mode 100644 index 0000000..42e7d5b --- /dev/null +++ b/docs/quickstarts/prisma.mdx @@ -0,0 +1,532 @@ +--- +title: "Connect Prisma to Xata" +description: "Learn how to connect your TypeScript/JavaScript application using Prisma to Xata's PostgreSQL platform. Get started with Prisma and PostgreSQL for type-safe database operations." +--- + +# Connect Prisma to Xata + +Learn how to use Prisma with Xata's PostgreSQL platform. Prisma is a next-generation TypeScript ORM that provides type-safe database access, auto-generated queries, and excellent developer experience. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of TypeScript and Prisma + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Project + +Create a new Node.js project: + +```bash +mkdir my-xata-app +cd my-xata-app +npm init -y +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install Prisma and PostgreSQL driver: + +```bash +npm install prisma @prisma/client +npm install -D typescript @types/node +``` + +## Initialize Prisma + +Initialize Prisma in your project: + +```bash +npx prisma init +``` + +## Configure Prisma Schema + +Update your `prisma/schema.prisma` file: + +```prisma +// prisma/schema.prisma +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Product { + id Int @id @default(autoincrement()) + name String + price Decimal @db.Decimal(7, 2) + rating Int? + orderItems OrderItem[] + + @@map("products") +} + +model Order { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) @map("created_at") + orderItems OrderItem[] + + @@map("orders") +} + +model OrderItem { + orderId Int + productId Int + qty Int + + order Order @relation(fields: [orderId], references: [id], onDelete: Cascade) + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + + @@id([orderId, productId]) + @@map("order_items") +} +``` + +## Generate Prisma Client + +Generate the Prisma client based on your schema: + +```bash +npx prisma generate +``` + +## Create Database Service + +Create a database service file: + +```typescript +// lib/prisma.ts +import { PrismaClient } from '@prisma/client' + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined +} + +export const prisma = globalForPrisma.prisma ?? new PrismaClient() + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma +``` + +## Create Service Layer + +Create services for database operations: + +```typescript +// services/productService.ts +import { prisma } from '../lib/prisma' +import { Decimal } from '@prisma/client/runtime/library' + +export class ProductService { + async getAllProducts() { + return await prisma.product.findMany() + } + + async getProductById(id: number) { + return await prisma.product.findUnique({ + where: { id } + }) + } + + async createProduct(data: { + name: string + price: number + rating?: number + }) { + return await prisma.product.create({ + data: { + name: data.name, + price: new Decimal(data.price), + rating: data.rating + } + }) + } + + async updateProduct(id: number, data: { + name?: string + price?: number + rating?: number + }) { + const updateData: any = {} + if (data.name) updateData.name = data.name + if (data.price) updateData.price = new Decimal(data.price) + if (data.rating !== undefined) updateData.rating = data.rating + + return await prisma.product.update({ + where: { id }, + data: updateData + }) + } + + async deleteProduct(id: number) { + return await prisma.product.delete({ + where: { id } + }) + } + + async getProductsByRating(rating: number) { + return await prisma.product.findMany({ + where: { rating } + }) + } + + async getProductsByPriceRange(minPrice: number, maxPrice: number) { + return await prisma.product.findMany({ + where: { + price: { + gte: new Decimal(minPrice), + lte: new Decimal(maxPrice) + } + } + }) + } +} +``` + +```typescript +// services/orderService.ts +import { prisma } from '../lib/prisma' + +export class OrderService { + async getAllOrders() { + return await prisma.order.findMany({ + include: { + orderItems: { + include: { + product: true + } + } + } + }) + } + + async getOrderById(id: number) { + return await prisma.order.findUnique({ + where: { id }, + include: { + orderItems: { + include: { + product: true + } + } + } + }) + } + + async createOrder() { + return await prisma.order.create({}) + } + + async addItemToOrder(orderId: number, productId: number, qty: number) { + return await prisma.orderItem.create({ + data: { + orderId, + productId, + qty + }, + include: { + product: true + } + }) + } + + async getOrdersWithProductDetails() { + return await prisma.$queryRaw` + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + p.price, + oi.qty, + (p.price * oi.qty) as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + ORDER BY o.created_at DESC + ` + } +} +``` + +## Create Sample Application + +Create a simple application to demonstrate the functionality: + +```typescript +// app.ts +import { ProductService } from './services/productService' +import { OrderService } from './services/orderService' + +async function main() { + const productService = new ProductService() + const orderService = new OrderService() + + try { + // Create sample products + console.log('Creating sample products...') + const products = await Promise.all([ + productService.createProduct({ + name: 'Wireless Headphones', + price: 99.99, + rating: 5 + }), + productService.createProduct({ + name: 'Smartphone', + price: 699.99, + rating: 4 + }), + productService.createProduct({ + name: 'Laptop', + price: 1299.99, + rating: 5 + }), + productService.createProduct({ + name: 'Coffee Maker', + price: 89.99, + rating: 4 + }) + ]) + + console.log(`Created ${products.length} products`) + + // Display all products + console.log('\nAll products:') + const allProducts = await productService.getAllProducts() + for (const product of allProducts) { + console.log(`- ${product.name}: $${product.price} (Rating: ${product.rating}/5)`) + } + + // Get products by rating + console.log('\nTop-rated products (5 stars):') + const topProducts = await productService.getProductsByRating(5) + for (const product of topProducts) { + console.log(`- ${product.name}: $${product.price}`) + } + + // Create an order with items + console.log('\nCreating an order...') + const order = await orderService.createOrder() + console.log(`Created order #${order.id}`) + + // Add items to order + await orderService.addItemToOrder(order.id, products[0].id, 2) // 2 headphones + await orderService.addItemToOrder(order.id, products[2].id, 1) // 1 laptop + + // Get order details with products + console.log('\nOrder details:') + const orderDetails = await orderService.getOrdersWithProductDetails() + for (const detail of orderDetails as any[]) { + console.log(`Order #${detail.order_id}: ${detail.product_name} x${detail.qty} = $${detail.total}`) + } + + // Update a product + console.log('\nUpdating product...') + const updatedProduct = await productService.updateProduct(products[0].id, { + name: 'Premium Wireless Headphones', + price: 129.99 + }) + console.log(`Updated: ${updatedProduct.name} - $${updatedProduct.price}`) + + } catch (error) { + console.error('Error:', error) + } finally { + await prisma.$disconnect() + } +} + +main() +``` + +## Create TypeScript Configuration + +Create a `tsconfig.json` file: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +## Create Package Scripts + +Update your `package.json` scripts: + +```json +{ + "scripts": { + "build": "tsc", + "start": "node dist/app.js", + "dev": "ts-node app.ts", + "db:generate": "prisma generate", + "db:push": "prisma db push", + "db:studio": "prisma studio" + } +} +``` + +## Run the Application + +Install dependencies and run the application: + +```bash +npm install ts-node +npm run dev +``` + +## Create Express API (Optional) + +For a web API, you can integrate with Express: + +```typescript +// api.ts +import express from 'express' +import { ProductService } from './services/productService' +import { OrderService } from './services/orderService' + +const app = express() +app.use(express.json()) + +const productService = new ProductService() +const orderService = new OrderService() + +// Product routes +app.get('/api/products', async (req, res) => { + try { + const products = await productService.getAllProducts() + res.json(products) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch products' }) + } +}) + +app.get('/api/products/:id', async (req, res) => { + try { + const product = await productService.getProductById(parseInt(req.params.id)) + if (!product) { + return res.status(404).json({ error: 'Product not found' }) + } + res.json(product) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch product' }) + } +}) + +app.post('/api/products', async (req, res) => { + try { + const product = await productService.createProduct(req.body) + res.status(201).json(product) + } catch (error) { + res.status(500).json({ error: 'Failed to create product' }) + } +}) + +app.get('/api/products/rating/:rating', async (req, res) => { + try { + const products = await productService.getProductsByRating(parseInt(req.params.rating)) + res.json(products) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch products' }) + } +}) + +// Order routes +app.get('/api/orders', async (req, res) => { + try { + const orders = await orderService.getAllOrders() + res.json(orders) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch orders' }) + } +}) + +const PORT = process.env.PORT || 3000 +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`) +}) +``` + +To run the Express API: + +```bash +npm install express +npm install -D @types/express +npx ts-node api.ts +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/python.mdx b/docs/quickstarts/python.mdx new file mode 100644 index 0000000..bee785d --- /dev/null +++ b/docs/quickstarts/python.mdx @@ -0,0 +1,592 @@ +--- +title: "Connect Python to Xata" +description: "Learn how to connect your Python application to Xata's PostgreSQL platform using the native psycopg2 driver. Get started with Python and PostgreSQL for direct database operations." +--- + +# Connect Python to Xata + +Learn how to use Python with Xata's PostgreSQL platform using the native `psycopg2` driver. This approach gives you direct control over SQL queries and database operations without the abstraction of an ORM. + +## Prerequisites + +- Xata account and project setup +- Python 3.8+ installed +- Basic knowledge of Python and SQL + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Python Project + +Create a new Python project: + +```bash +mkdir my-xata-app +cd my-xata-app +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL driver and environment variable library: + +```bash +pip install psycopg2-binary python-decouple +``` + +## Create Database Connection + +Create a database connection module: + +```python +# db/connection.py +import os +import psycopg2 +from psycopg2.extras import RealDictCursor +from decouple import config +from contextlib import contextmanager + +class DatabaseConnection: + def __init__(self): + self.connection_string = config('DATABASE_URL') + if not self.connection_string: + raise ValueError("DATABASE_URL environment variable is required") + + @contextmanager + def get_connection(self): + """Context manager for database connections""" + conn = None + try: + conn = psycopg2.connect(self.connection_string) + yield conn + except Exception as e: + if conn: + conn.rollback() + raise e + finally: + if conn: + conn.close() + + @contextmanager + def get_cursor(self, cursor_factory=None): + """Context manager for database cursors""" + with self.get_connection() as conn: + cursor = conn.cursor(cursor_factory=cursor_factory) + try: + yield cursor + conn.commit() + except Exception as e: + conn.rollback() + raise e + finally: + cursor.close() + +# Global database instance +db = DatabaseConnection() +``` + +## Create Data Models + +Create simple data classes for your entities: + +```python +# models/product.py +from dataclasses import dataclass +from decimal import Decimal +from typing import Optional + +@dataclass +class Product: + id: Optional[int] + name: str + price: Decimal + rating: Optional[int] + + @classmethod + def from_dict(cls, data: dict): + return cls( + id=data.get('id'), + name=data['name'], + price=Decimal(str(data['price'])), + rating=data.get('rating') + ) + + def to_dict(self): + return { + 'id': self.id, + 'name': self.name, + 'price': float(self.price), + 'rating': self.rating + } +``` + +```python +# models/order.py +from dataclasses import dataclass +from datetime import datetime +from typing import Optional, List +from .product import Product + +@dataclass +class Order: + id: Optional[int] + created_at: Optional[datetime] + order_items: Optional[List['OrderItem']] = None + + @classmethod + def from_dict(cls, data: dict): + return cls( + id=data.get('id'), + created_at=data.get('created_at') + ) + + def to_dict(self): + return { + 'id': self.id, + 'created_at': self.created_at.isoformat() if self.created_at else None + } + +@dataclass +class OrderItem: + order_id: int + product_id: int + qty: int + product: Optional[Product] = None + + @classmethod + def from_dict(cls, data: dict): + return cls( + order_id=data['order_id'], + product_id=data['product_id'], + qty=data['qty'], + product=Product.from_dict(data['product']) if data.get('product') else None + ) + + def to_dict(self): + return { + 'order_id': self.order_id, + 'product_id': self.product_id, + 'qty': self.qty, + 'product': self.product.to_dict() if self.product else None + } +``` + +## Create Service Layer + +Create service classes for database operations: + +```python +# services/product_service.py +from decimal import Decimal +from typing import List, Optional +from db.connection import db +from models.product import Product + +class ProductService: + def get_all_products(self) -> List[Product]: + """Get all products from the database""" + with db.get_cursor() as cursor: + cursor.execute("SELECT id, name, price, rating FROM products ORDER BY id") + products = [] + for row in cursor.fetchall(): + products.append(Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + )) + return products + + def get_product_by_id(self, product_id: int) -> Optional[Product]: + """Get a product by its ID""" + with db.get_cursor() as cursor: + cursor.execute( + "SELECT id, name, price, rating FROM products WHERE id = %s", + (product_id,) + ) + row = cursor.fetchone() + if row: + return Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + ) + return None + + def create_product(self, name: str, price: Decimal, rating: Optional[int] = None) -> Product: + """Create a new product""" + with db.get_cursor() as cursor: + cursor.execute( + "INSERT INTO products (name, price, rating) VALUES (%s, %s, %s) RETURNING id, name, price, rating", + (name, price, rating) + ) + row = cursor.fetchone() + return Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + ) + + def update_product(self, product_id: int, name: Optional[str] = None, + price: Optional[Decimal] = None, rating: Optional[int] = None) -> Optional[Product]: + """Update an existing product""" + # Build dynamic update query + updates = [] + params = [] + + if name is not None: + updates.append("name = %s") + params.append(name) + if price is not None: + updates.append("price = %s") + params.append(price) + if rating is not None: + updates.append("rating = %s") + params.append(rating) + + if not updates: + return self.get_product_by_id(product_id) + + params.append(product_id) + query = f"UPDATE products SET {', '.join(updates)} WHERE id = %s RETURNING id, name, price, rating" + + with db.get_cursor() as cursor: + cursor.execute(query, params) + row = cursor.fetchone() + if row: + return Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + ) + return None + + def delete_product(self, product_id: int) -> bool: + """Delete a product by ID""" + with db.get_cursor() as cursor: + cursor.execute("DELETE FROM products WHERE id = %s", (product_id,)) + return cursor.rowcount > 0 + + def get_products_by_rating(self, rating: int) -> List[Product]: + """Get products by rating""" + with db.get_cursor() as cursor: + cursor.execute( + "SELECT id, name, price, rating FROM products WHERE rating = %s ORDER BY name", + (rating,) + ) + products = [] + for row in cursor.fetchall(): + products.append(Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + )) + return products + + def get_products_by_price_range(self, min_price: Decimal, max_price: Decimal) -> List[Product]: + """Get products within a price range""" + with db.get_cursor() as cursor: + cursor.execute( + "SELECT id, name, price, rating FROM products WHERE price >= %s AND price <= %s ORDER BY price", + (min_price, max_price) + ) + products = [] + for row in cursor.fetchall(): + products.append(Product( + id=row[0], + name=row[1], + price=row[2], + rating=row[3] + )) + return products +``` + +```python +# services/order_service.py +from typing import List, Optional, Dict, Any +from db.connection import db +from models.order import Order, OrderItem +from models.product import Product + +class OrderService: + def get_all_orders(self) -> List[Order]: + """Get all orders""" + with db.get_cursor() as cursor: + cursor.execute("SELECT id, created_at FROM orders ORDER BY created_at DESC") + orders = [] + for row in cursor.fetchall(): + orders.append(Order( + id=row[0], + created_at=row[1] + )) + return orders + + def get_order_by_id(self, order_id: int) -> Optional[Order]: + """Get an order by ID with its items""" + with db.get_cursor() as cursor: + # Get order details + cursor.execute("SELECT id, created_at FROM orders WHERE id = %s", (order_id,)) + row = cursor.fetchone() + if not row: + return None + + order = Order(id=row[0], created_at=row[1]) + + # Get order items with product details + cursor.execute(""" + SELECT oi.order_id, oi.product_id, oi.qty, + p.id as product_id, p.name, p.price, p.rating + FROM order_items oi + JOIN products p ON oi.product_id = p.id + WHERE oi.order_id = %s + """, (order_id,)) + + order_items = [] + for item_row in cursor.fetchall(): + product = Product( + id=item_row[3], + name=item_row[4], + price=item_row[5], + rating=item_row[6] + ) + order_item = OrderItem( + order_id=item_row[0], + product_id=item_row[1], + qty=item_row[2], + product=product + ) + order_items.append(order_item) + + order.order_items = order_items + return order + + def create_order(self) -> Order: + """Create a new order""" + with db.get_cursor() as cursor: + cursor.execute( + "INSERT INTO orders DEFAULT VALUES RETURNING id, created_at" + ) + row = cursor.fetchone() + return Order( + id=row[0], + created_at=row[1] + ) + + def add_item_to_order(self, order_id: int, product_id: int, qty: int) -> Optional[OrderItem]: + """Add an item to an order""" + with db.get_cursor() as cursor: + # Check if order and product exist + cursor.execute("SELECT id FROM orders WHERE id = %s", (order_id,)) + if not cursor.fetchone(): + return None + + cursor.execute("SELECT id FROM products WHERE id = %s", (product_id,)) + if not cursor.fetchone(): + return None + + # Add order item + cursor.execute( + "INSERT INTO order_items (order_id, product_id, qty) VALUES (%s, %s, %s) RETURNING order_id, product_id, qty", + (order_id, product_id, qty) + ) + row = cursor.fetchone() + return OrderItem( + order_id=row[0], + product_id=row[1], + qty=row[2] + ) + + def get_orders_with_product_details(self) -> List[Dict[str, Any]]: + """Get orders with product details and calculated totals""" + with db.get_cursor() as cursor: + cursor.execute(""" + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + p.price, + oi.qty, + (p.price * oi.qty) as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + ORDER BY o.created_at DESC + """) + + results = [] + for row in cursor.fetchall(): + results.append({ + 'order_id': row[0], + 'created_at': row[1], + 'product_name': row[2], + 'price': float(row[3]), + 'qty': row[4], + 'total': float(row[5]) + }) + return results +``` + +## Create Sample Application + +Create a simple application to demonstrate the functionality: + +```python +# main.py +from decimal import Decimal +from services.product_service import ProductService +from services.order_service import OrderService + +def main(): + product_service = ProductService() + order_service = OrderService() + + try: + # Create sample products + print("Creating sample products...") + products = [ + product_service.create_product("Wireless Headphones", Decimal("99.99"), 5), + product_service.create_product("Smartphone", Decimal("699.99"), 4), + product_service.create_product("Laptop", Decimal("1299.99"), 5), + product_service.create_product("Coffee Maker", Decimal("89.99"), 4) + ] + + print(f"Created {len(products)} products") + + # Display all products + print("\nAll products:") + all_products = product_service.get_all_products() + for product in all_products: + rating = product.rating if product.rating else "N/A" + print(f"- {product.name}: ${product.price} (Rating: {rating}/5)") + + # Get products by rating + print("\nTop-rated products (5 stars):") + top_products = product_service.get_products_by_rating(5) + for product in top_products: + print(f"- {product.name}: ${product.price}") + + # Create an order with items + print("\nCreating an order...") + order = order_service.create_order() + print(f"Created order #{order.id}") + + # Add items to order + if products: + order_service.add_item_to_order(order.id, products[0].id, 2) # 2 headphones + if len(products) >= 3: + order_service.add_item_to_order(order.id, products[2].id, 1) # 1 laptop + + # Get order details with products + print("\nOrder details:") + order_details = order_service.get_orders_with_product_details() + for detail in order_details: + print(f"Order #{detail['order_id']}: {detail['product_name']} x{detail['qty']} = ${detail['total']}") + + # Update a product + if products: + print("\nUpdating product...") + updated_product = product_service.update_product( + products[0].id, + name="Premium Wireless Headphones", + price=Decimal("129.99") + ) + if updated_product: + print(f"Updated: {updated_product.name} - ${updated_product.price}") + + # Get products by price range + print("\nProducts between $50 and $150:") + mid_range_products = product_service.get_products_by_price_range( + Decimal("50.00"), Decimal("150.00") + ) + for product in mid_range_products: + print(f"- {product.name}: ${product.price}") + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main() +``` + +## Create Requirements File + +Create a `requirements.txt` file: + +```txt +# requirements.txt +psycopg2-binary>=2.9.0 +python-decouple>=3.8 +``` + +## Run the Application + +Install dependencies and run the application: + +```bash +pip install -r requirements.txt +python main.py +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/quickstarts.mdx b/docs/quickstarts/quickstarts.mdx new file mode 100644 index 0000000..c3ea32e --- /dev/null +++ b/docs/quickstarts/quickstarts.mdx @@ -0,0 +1,71 @@ +--- +title: Quickstarts +description: Get started with Xata PostgreSQL using your favorite frameworks and ORMs +--- + +# Quickstarts + +Connect your application to Xata's PostgreSQL platform using your preferred framework or ORM. Each quickstart uses a common e-commerce dataset to demonstrate database operations, relationships, and best practices. + +## Common Dataset + +All quickstarts use the same e-commerce dataset: + +- **products** - Product catalog with name, price, and rating +- **orders** - Customer orders with timestamps +- **order_items** - Order line items linking products to orders + +This dataset demonstrates: +- Primary and foreign key relationships +- CRUD operations +- Complex queries with joins +- Data validation and constraints + +## Prerequisites + +Before starting any quickstart: + +1. [Create a Xata account](https://console.xata.io) and project +2. [Set up your first branch](/getting-started#3-create-a-branch) +3. Install your chosen framework or ORM + +## Frontend Frameworks + +Build modern web applications with Xata PostgreSQL: + +- **[Next.js](/quickstarts/nextjs)** - React framework with server-side rendering +- **[Nuxt](/quickstarts/nuxt)** - Vue.js framework for universal applications +- **[Astro](/quickstarts/astro)** - Static site generator with dynamic content +- **[Svelte](/quickstarts/svelte)** - Component framework with reactive UI +- **[Remix](/quickstarts/remix)** - Full-stack React framework + +## Backend Frameworks + +Create robust APIs and services: + +- **[Node.js](/quickstarts/nodejs)** - JavaScript runtime with Express +- **[Django](/quickstarts/django)** - Python web framework +- **[Rails](/quickstarts/rails)** - Ruby web framework +- **[Laravel](/quickstarts/laravel)** - PHP web framework +- **[Go](/quickstarts/go)** - Go programming language +- **[Rust](/quickstarts/rust)** - Rust programming language +- **[Java](/quickstarts/java)** - Java with Spring Boot + +## ORMs and Query Builders + +Work with your data using powerful ORMs: + +- **[Drizzle](/quickstarts/drizzle)** - TypeScript ORM with type safety +- **[SQLAlchemy](/quickstarts/sqlalchemy)** - Python SQL toolkit and ORM +- **[Prisma](/quickstarts/prisma)** - Next-generation TypeScript ORM +- **[TypeORM](/quickstarts/typeorm)** - TypeScript ORM for Node.js +- **[GORM](/quickstarts/gorm)** - Go ORM library +- **[JPA/Hibernate](/quickstarts/jpa)** - Java persistence framework + +## Getting Help + +Need assistance with your integration? + +- Join our [Discord community](https://discord.gg/xata) for real-time help +- Explore [core concepts](/core-concepts) to understand Xata's features +- Check out [tutorials](/tutorials) for advanced workflows \ No newline at end of file diff --git a/docs/quickstarts/rails.mdx b/docs/quickstarts/rails.mdx new file mode 100644 index 0000000..74c64a7 --- /dev/null +++ b/docs/quickstarts/rails.mdx @@ -0,0 +1,348 @@ +--- +title: "Connect Rails to Xata" +description: "Learn how to connect your Ruby on Rails application to Xata's PostgreSQL platform. Get started with Rails and PostgreSQL for rapid web development." +--- + +# Connect Rails to Xata + +Learn how to use Ruby on Rails with Xata's PostgreSQL platform. Rails is a web application framework that provides excellent conventions and rapid development capabilities. + +## Prerequisites + +- Xata account and project setup +- Ruby 3.0+ installed +- Basic knowledge of Ruby and Rails + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Rails Project + +Create a new Rails project with PostgreSQL: + +```bash +rails new my_xata_app --database=postgresql --api +cd my_xata_app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Add the dotenv gem to your Gemfile: + +```ruby +# Gemfile +source "https://rubygems.org" + +# ... existing gems ... + +gem 'dotenv-rails', groups: [:development, :test] +``` + +Install the gems: + +```bash +bundle install +``` + +## Configure Database Connection + +Update your `config/database.yml` to use environment variables: + +```yaml +# config/database.yml +default: &default + adapter: postgresql + encoding: unicode + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + url: <%= ENV['DATABASE_URL'] %> + +test: + <<: *default + url: <%= ENV['DATABASE_URL'] %> + +production: + <<: *default + url: <%= ENV['DATABASE_URL'] %> +``` + +Load environment variables in `config/application.rb`: + +```ruby +# config/application.rb +require_relative "boot" +require "rails/all" + +Bundler.require(*Rails.groups) + +module MyXataApp + class Application < Rails::Application + config.load_defaults 7.0 + + # Load environment variables + Dotenv::Railtie.load if Rails.env.development? || Rails.env.test? + end +end +``` + +## Create Models + +Generate your models: + +```bash +rails generate model Product name:string price:decimal rating:integer +rails generate model Order created_at:datetime +rails generate model OrderItem order:references product:references qty:integer +``` + +Update the generated models: + +```ruby +# app/models/product.rb +class Product < ApplicationRecord + has_many :order_items, dependent: :destroy + has_many :orders, through: :order_items + + validates :name, presence: true + validates :price, presence: true, numericality: { greater_than: 0 } + validates :rating, numericality: { only_integer: true, greater_than: 0, less_than: 6 }, allow_nil: true +end +``` + +```ruby +# app/models/order.rb +class Order < ApplicationRecord + has_many :order_items, dependent: :destroy + has_many :products, through: :order_items +end +``` + +```ruby +# app/models/order_item.rb +class OrderItem < ApplicationRecord + belongs_to :order + belongs_to :product + + validates :qty, presence: true, numericality: { greater_than: 0 } + validates :order_id, uniqueness: { scope: :product_id } +end +``` + +## Create Controllers + +Generate API controllers: + +```bash +rails generate controller Api::Products index show create +rails generate controller Api::Orders index +``` + +Update the controllers: + +```ruby +# app/controllers/api/products_controller.rb +class Api::ProductsController < ApplicationController + def index + products = Product.all + render json: products + end + + def show + product = Product.find(params[:id]) + render json: product + rescue ActiveRecord::RecordNotFound + render json: { error: 'Product not found' }, status: :not_found + end + + def create + product = Product.new(product_params) + if product.save + render json: product, status: :created + else + render json: { errors: product.errors.full_messages }, status: :unprocessable_entity + end + end + + private + + def product_params + params.require(:product).permit(:name, :price, :rating) + end +end +``` + +```ruby +# app/controllers/api/orders_controller.rb +class Api::OrdersController < ApplicationController + def index + orders = Order.joins(:order_items, :products) + .select('orders.id as order_id, orders.created_at, products.name as product_name, order_items.qty, (products.price * order_items.qty) as total') + + render json: orders + end +end +``` + +## Configure Routes + +Update your `config/routes.rb`: + +```ruby +# config/routes.rb +Rails.application.routes.draw do + namespace :api do + resources :products, only: [:index, :show, :create] + resources :orders, only: [:index] + end +end +``` + +## Run Migrations + +Since you're using an existing database schema, you can create a migration that matches your existing tables: + +```bash +rails generate migration CreateExistingTables +``` + +Edit the generated migration: + +```ruby +# db/migrate/YYYYMMDDHHMMSS_create_existing_tables.rb +class CreateExistingTables < ActiveRecord::Migration[7.0] + def up + # Create products table if it doesn't exist + unless table_exists?(:products) + create_table :products do |t| + t.string :name, null: false + t.decimal :price, precision: 7, scale: 2, null: false + t.integer :rating + + t.timestamps + end + end + + # Create orders table if it doesn't exist + unless table_exists?(:orders) + create_table :orders do |t| + t.timestamps + end + end + + # Create order_items table if it doesn't exist + unless table_exists?(:order_items) + create_table :order_items do |t| + t.references :order, null: false, foreign_key: true + t.references :product, null: false, foreign_key: true + t.integer :qty, null: false + + t.timestamps + end + + add_index :order_items, [:order_id, :product_id], unique: true + end + end + + def down + drop_table :order_items if table_exists?(:order_items) + drop_table :orders if table_exists?(:orders) + drop_table :products if table_exists?(:products) + end +end +``` + +Run the migration: + +```bash +rails db:migrate +``` + +## Run the Application + +Start your development server: + +```bash +rails server +``` + +Your API will be available at: +- `http://localhost:3000/api/products` +- `http://localhost:3000/api/orders` + +## Test Your API + +You can test the endpoints using curl: + +```bash +# Get all products +curl http://localhost:3000/api/products + +# Create a new product +curl -X POST http://localhost:3000/api/products \ + -H "Content-Type: application/json" \ + -d '{"product":{"name":"Wireless Headphones","price":99.99,"rating":5}}' + +# Get orders with products +curl http://localhost:3000/api/orders +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/remix.mdx b/docs/quickstarts/remix.mdx new file mode 100644 index 0000000..bd6d608 --- /dev/null +++ b/docs/quickstarts/remix.mdx @@ -0,0 +1,167 @@ +--- +title: "Connect Remix to Xata" +description: "Learn how to connect your Remix application to Xata's PostgreSQL platform. Get started with Remix and PostgreSQL for modern, full-stack web apps." +--- + +# Connect Remix to Xata + +Learn how to use Remix with Xata's PostgreSQL platform. Remix is a full-stack web framework for building fast, dynamic applications with server-side rendering and API endpoints. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of Remix + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Remix Project + +Create a new Remix project: + +```bash +npx create-remix@latest +cd my-xata-app +npm install +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL client: + +```bash +npm install postgres +npm install -D @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// app/db.ts +import postgres from 'postgres'; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create Loader for Products + +Create a loader to fetch products: + +```typescript +// app/routes/_index.tsx +import type { LoaderFunction } from '@remix-run/node'; +import { json } from '@remix-run/node'; +import { sql } from '~/db'; + +export const loader: LoaderFunction = async () => { + try { + const products = await sql`SELECT * FROM products`; + return json({ products }); + } catch (error) { + return json({ error: 'Failed to fetch products' }, { status: 500 }); + } +}; + +export default function Index() { + const { products } = useLoaderData(); + return ( +
+

Xata E-commerce Store

+
+ {products.map((product) => ( +
+

{product.name}

+

${product.price}

+ {product.rating && ( +
+ + {product.rating}/5 +
+ )} +
+ ))} +
+
+ ); +} +``` + + + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Visit `http://localhost:3000` to see your application. + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/rust.mdx b/docs/quickstarts/rust.mdx new file mode 100644 index 0000000..b120723 --- /dev/null +++ b/docs/quickstarts/rust.mdx @@ -0,0 +1,361 @@ +--- +title: "Connect Rust to Xata" +description: "Learn how to connect your Rust application to Xata's PostgreSQL platform. Get started with Rust and PostgreSQL for high-performance, memory-safe applications." +--- + +# Connect Rust to Xata + +Learn how to use Rust with Xata's PostgreSQL platform. Rust provides excellent performance, memory safety, and concurrency features for building reliable backend services. + +## Prerequisites + +- Xata account and project setup +- Rust 1.70+ installed +- Basic knowledge of Rust + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Rust Project + +Create a new Rust project: + +```bash +cargo new my-xata-app +cd my-xata-app +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Configure Dependencies + +Update your `Cargo.toml`: + +```toml +[package] +name = "my-xata-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix-web = "4.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "decimal"] } +tokio = { version = "1.0", features = ["full"] } +chrono = { version = "0.4", features = ["serde"] } +rust_decimal = { version = "1.32", features = ["serde"] } +dotenv = "0.15" +``` + +## Create Models + +Create your data structures: + +```rust +// src/models.rs +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; +use rust_decimal::Decimal; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Product { + pub id: i32, + pub name: String, + pub price: Decimal, + pub rating: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateProduct { + pub name: String, + pub price: Decimal, + pub rating: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Order { + pub id: i32, + pub created_at: DateTime, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct OrderItem { + pub order_id: i32, + pub product_id: i32, + pub qty: i32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct OrderWithProducts { + pub order_id: i32, + pub created_at: DateTime, + pub product_name: String, + pub qty: i32, + pub total: Decimal, +} +``` + +## Create Database Module + +Create database connection and queries: + +```rust +// src/db.rs +use sqlx::PgPool; +use crate::models::{Product, CreateProduct, OrderWithProducts}; + +pub async fn create_pool() -> Result { + let database_url = std::env::var("DATABASE_URL") + .expect("DATABASE_URL must be set"); + + PgPool::connect(&database_url).await +} + +pub async fn get_products(pool: &PgPool) -> Result, sqlx::Error> { + sqlx::query_as!( + Product, + "SELECT id, name, price, rating FROM products" + ) + .fetch_all(pool) + .await +} + +pub async fn get_product(pool: &PgPool, id: i32) -> Result, sqlx::Error> { + sqlx::query_as!( + Product, + "SELECT id, name, price, rating FROM products WHERE id = $1", + id + ) + .fetch_optional(pool) + .await +} + +pub async fn create_product(pool: &PgPool, product: CreateProduct) -> Result { + sqlx::query_as!( + Product, + "INSERT INTO products (name, price, rating) VALUES ($1, $2, $3) RETURNING id, name, price, rating", + product.name, + product.price, + product.rating + ) + .fetch_one(pool) + .await +} + +pub async fn get_orders_with_products(pool: &PgPool) -> Result, sqlx::Error> { + sqlx::query_as!( + OrderWithProducts, + r#" + SELECT + o.id as order_id, + o.created_at, + p.name as product_name, + oi.qty, + p.price * oi.qty as total + FROM orders o + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + "# + ) + .fetch_all(pool) + .await +} +``` + +## Create Handlers + +Create HTTP handlers: + +```rust +// src/handlers.rs +use actix_web::{web, HttpResponse, Result}; +use sqlx::PgPool; +use crate::models::{CreateProduct, Product, OrderWithProducts}; +use crate::db; + +pub async fn get_products(pool: web::Data) -> Result { + match db::get_products(&pool).await { + Ok(products) => Ok(HttpResponse::Ok().json(products)), + Err(e) => { + eprintln!("Database error: {}", e); + Ok(HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "Failed to fetch products" + }))) + } + } +} + +pub async fn get_product( + pool: web::Data, + path: web::Path +) -> Result { + let id = path.into_inner(); + + match db::get_product(&pool, id).await { + Ok(Some(product)) => Ok(HttpResponse::Ok().json(product)), + Ok(None) => Ok(HttpResponse::NotFound().json(serde_json::json!({ + "error": "Product not found" + }))), + Err(e) => { + eprintln!("Database error: {}", e); + Ok(HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "Failed to fetch product" + }))) + } + } +} + +pub async fn create_product( + pool: web::Data, + product: web::Json +) -> Result { + match db::create_product(&pool, product.into_inner()).await { + Ok(product) => Ok(HttpResponse::Created().json(product)), + Err(e) => { + eprintln!("Database error: {}", e); + Ok(HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "Failed to create product" + }))) + } + } +} + +pub async fn get_orders(pool: web::Data) -> Result { + match db::get_orders_with_products(&pool).await { + Ok(orders) => Ok(HttpResponse::Ok().json(orders)), + Err(e) => { + eprintln!("Database error: {}", e); + Ok(HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "Failed to fetch orders" + }))) + } + } +} +``` + +## Create Main Application + +Create your main application file: + +```rust +// src/main.rs +use actix_web::{web, App, HttpServer}; +use dotenv::dotenv; + +mod models; +mod db; +mod handlers; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + dotenv().ok(); + + let pool = db::create_pool() + .await + .expect("Failed to create database pool"); + + println!("Server starting on http://localhost:3000"); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(pool.clone())) + .service( + web::scope("/api") + .route("/products", web::get().to(handlers::get_products)) + .route("/products/{id}", web::get().to(handlers::get_product)) + .route("/products", web::post().to(handlers::create_product)) + .route("/orders", web::get().to(handlers::get_orders)) + ) + }) + .bind("127.0.0.1:3000")? + .run() + .await +} +``` + +## Run the Application + +Start your development server: + +```bash +cargo run +``` + +Your API will be available at: +- `http://localhost:3000/api/products` +- `http://localhost:3000/api/orders` + +## Test Your API + +You can test the endpoints using curl: + +```bash +# Get all products +curl http://localhost:3000/api/products + +# Create a new product +curl -X POST http://localhost:3000/api/products \ + -H "Content-Type: application/json" \ + -d '{"name":"Wireless Headphones","price":"99.99","rating":5}' + +# Get orders with products +curl http://localhost:3000/api/orders +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/sqlalchemy.mdx b/docs/quickstarts/sqlalchemy.mdx new file mode 100644 index 0000000..71b0ca4 --- /dev/null +++ b/docs/quickstarts/sqlalchemy.mdx @@ -0,0 +1,452 @@ +--- +title: "Connect SQLAlchemy to Xata" +description: "Learn how to connect your Python application using SQLAlchemy to Xata's PostgreSQL platform. Get started with SQLAlchemy and PostgreSQL for robust data operations." +--- + +# Connect SQLAlchemy to Xata + +Learn how to use SQLAlchemy with Xata's PostgreSQL platform. SQLAlchemy is a powerful Python SQL toolkit and Object-Relational Mapping (ORM) library that provides a set of high-level API for database operations. + +## Prerequisites + +- Xata account and project setup +- Python 3.8+ installed +- Basic knowledge of Python and SQLAlchemy + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Project + +Create a new Python project: + +```bash +mkdir my-xata-app +cd my-xata-app +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install SQLAlchemy and PostgreSQL driver: + +```bash +pip install sqlalchemy psycopg2-binary python-decouple +``` + +## Configure Database Connection + +Create a database configuration file: + +```python +# db/config.py +import os +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from decouple import config + +DATABASE_URL = config('DATABASE_URL') + +if not DATABASE_URL: + raise ValueError("DATABASE_URL environment variable is required") + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() +``` + +## Define Models + +Create your SQLAlchemy models: + +```python +# models.py +from sqlalchemy import Column, Integer, String, Numeric, DateTime, ForeignKey, Table +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func +from db.config import Base + +# Association table for many-to-many relationship +order_items = Table( + 'order_items', + Base.metadata, + Column('order_id', Integer, ForeignKey('orders.id'), primary_key=True), + Column('product_id', Integer, ForeignKey('products.id'), primary_key=True), + Column('qty', Integer, nullable=False) +) + +class Product(Base): + __tablename__ = "products" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, nullable=False) + price = Column(Numeric(7, 2), nullable=False) + rating = Column(Integer) + + # Relationship + order_items = relationship("OrderItem", back_populates="product") + + def __repr__(self): + return f"" + +class Order(Base): + __tablename__ = "orders" + + id = Column(Integer, primary_key=True, index=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationship + order_items = relationship("OrderItem", back_populates="order") + + def __repr__(self): + return f"" + +class OrderItem(Base): + __tablename__ = "order_items" + + order_id = Column(Integer, ForeignKey("orders.id"), primary_key=True) + product_id = Column(Integer, ForeignKey("products.id"), primary_key=True) + qty = Column(Integer, nullable=False) + + # Relationships + order = relationship("Order", back_populates="order_items") + product = relationship("Product", back_populates="order_items") + + def __repr__(self): + return f"" +``` + +## Create Database Operations + +Create a service layer for database operations: + +```python +# services.py +from sqlalchemy.orm import Session +from sqlalchemy import select, func +from typing import List, Optional +from models import Product, Order, OrderItem +from decimal import Decimal + +class ProductService: + def __init__(self, db: Session): + self.db = db + + def get_all_products(self) -> List[Product]: + return self.db.query(Product).all() + + def get_product_by_id(self, product_id: int) -> Optional[Product]: + return self.db.query(Product).filter(Product.id == product_id).first() + + def create_product(self, name: str, price: Decimal, rating: Optional[int] = None) -> Product: + product = Product(name=name, price=price, rating=rating) + self.db.add(product) + self.db.commit() + self.db.refresh(product) + return product + + def update_product(self, product_id: int, name: str = None, price: Decimal = None, rating: int = None) -> Optional[Product]: + product = self.get_product_by_id(product_id) + if product: + if name is not None: + product.name = name + if price is not None: + product.price = price + if rating is not None: + product.rating = rating + self.db.commit() + self.db.refresh(product) + return product + + def delete_product(self, product_id: int) -> bool: + product = self.get_product_by_id(product_id) + if product: + self.db.delete(product) + self.db.commit() + return True + return False + + def get_products_by_rating(self, rating: int) -> List[Product]: + return self.db.query(Product).filter(Product.rating == rating).all() + + def get_products_by_price_range(self, min_price: Decimal, max_price: Decimal) -> List[Product]: + return self.db.query(Product).filter( + Product.price >= min_price, + Product.price <= max_price + ).all() + +class OrderService: + def __init__(self, db: Session): + self.db = db + + def get_all_orders(self) -> List[Order]: + return self.db.query(Order).all() + + def get_order_by_id(self, order_id: int) -> Optional[Order]: + return self.db.query(Order).filter(Order.id == order_id).first() + + def create_order(self) -> Order: + order = Order() + self.db.add(order) + self.db.commit() + self.db.refresh(order) + return order + + def add_item_to_order(self, order_id: int, product_id: int, qty: int) -> Optional[OrderItem]: + order = self.get_order_by_id(order_id) + product = self.db.query(Product).filter(Product.id == product_id).first() + + if order and product: + order_item = OrderItem(order_id=order_id, product_id=product_id, qty=qty) + self.db.add(order_item) + self.db.commit() + self.db.refresh(order_item) + return order_item + return None + + def get_order_with_items(self, order_id: int) -> Optional[Order]: + return self.db.query(Order).filter(Order.id == order_id).first() + + def get_orders_with_product_details(self) -> List[dict]: + """Get orders with product details and calculated totals""" + query = self.db.query( + Order.id.label('order_id'), + Order.created_at, + Product.name.label('product_name'), + Product.price, + OrderItem.qty, + (Product.price * OrderItem.qty).label('total') + ).join(OrderItem, Order.id == OrderItem.order_id)\ + .join(Product, OrderItem.product_id == Product.id) + + return [dict(row) for row in query.all()] +``` + +## Create Sample Application + +Create a simple application to demonstrate the functionality: + +```python +# app.py +from decimal import Decimal +from db.config import SessionLocal +from services import ProductService, OrderService +from models import Base, engine + +def main(): + # Create tables (in production, use migrations) + Base.metadata.create_all(bind=engine) + + # Get database session + db = SessionLocal() + + try: + # Initialize services + product_service = ProductService(db) + order_service = OrderService(db) + + # Create sample products + print("Creating sample products...") + products = [ + product_service.create_product("Wireless Headphones", Decimal("99.99"), 5), + product_service.create_product("Smartphone", Decimal("699.99"), 4), + product_service.create_product("Laptop", Decimal("1299.99"), 5), + product_service.create_product("Coffee Maker", Decimal("89.99"), 4), + ] + + print(f"Created {len(products)} products") + + # Display all products + print("\nAll products:") + all_products = product_service.get_all_products() + for product in all_products: + print(f"- {product.name}: ${product.price} (Rating: {product.rating}/5)") + + # Get products by rating + print("\nTop-rated products (5 stars):") + top_products = product_service.get_products_by_rating(5) + for product in top_products: + print(f"- {product.name}: ${product.price}") + + # Create an order with items + print("\nCreating an order...") + order = order_service.create_order() + print(f"Created order #{order.id}") + + # Add items to order + order_service.add_item_to_order(order.id, products[0].id, 2) # 2 headphones + order_service.add_item_to_order(order.id, products[2].id, 1) # 1 laptop + + # Get order details with products + print("\nOrder details:") + order_details = order_service.get_orders_with_product_details() + for detail in order_details: + print(f"Order #{detail['order_id']}: {detail['product_name']} x{detail['qty']} = ${detail['total']}") + + # Update a product + print("\nUpdating product...") + updated_product = product_service.update_product( + products[0].id, + name="Premium Wireless Headphones", + price=Decimal("129.99") + ) + print(f"Updated: {updated_product.name} - ${updated_product.price}") + + finally: + db.close() + +if __name__ == "__main__": + main() +``` + +## Create Requirements File + +Create a `requirements.txt` file: + +```txt +# requirements.txt +sqlalchemy>=2.0.0 +psycopg2-binary>=2.9.0 +python-decouple>=3.8 +``` + +## Run the Application + +Install dependencies and run the application: + +```bash +pip install -r requirements.txt +python app.py +``` + +## Create FastAPI Integration (Optional) + +For a web API, you can integrate with FastAPI: + +```python +# api.py +from fastapi import FastAPI, Depends, HTTPException +from sqlalchemy.orm import Session +from typing import List +from decimal import Decimal +from pydantic import BaseModel + +from db.config import get_db +from services import ProductService, OrderService +from models import Product, Order + +app = FastAPI(title="Xata SQLAlchemy API") + +# Pydantic models for API +class ProductCreate(BaseModel): + name: str + price: Decimal + rating: int = None + +class ProductResponse(BaseModel): + id: int + name: str + price: Decimal + rating: int = None + + class Config: + from_attributes = True + +@app.get("/products", response_model=List[ProductResponse]) +def get_products(db: Session = Depends(get_db)): + service = ProductService(db) + return service.get_all_products() + +@app.get("/products/{product_id}", response_model=ProductResponse) +def get_product(product_id: int, db: Session = Depends(get_db)): + service = ProductService(db) + product = service.get_product_by_id(product_id) + if not product: + raise HTTPException(status_code=404, detail="Product not found") + return product + +@app.post("/products", response_model=ProductResponse) +def create_product(product: ProductCreate, db: Session = Depends(get_db)): + service = ProductService(db) + return service.create_product(product.name, product.price, product.rating) + +@app.get("/products/rating/{rating}", response_model=List[ProductResponse]) +def get_products_by_rating(rating: int, db: Session = Depends(get_db)): + service = ProductService(db) + return service.get_products_by_rating(rating) +``` + +To run the FastAPI version: + +```bash +pip install fastapi uvicorn +uvicorn api:app --reload +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/svelte.mdx b/docs/quickstarts/svelte.mdx new file mode 100644 index 0000000..8d5c543 --- /dev/null +++ b/docs/quickstarts/svelte.mdx @@ -0,0 +1,184 @@ +--- +title: "Connect SvelteKit to Xata" +description: "Learn how to connect your SvelteKit application to Xata's PostgreSQL platform. Get started with SvelteKit and PostgreSQL for fast, modern web apps." +--- + +# Connect SvelteKit to Xata + +Learn how to use SvelteKit with Xata's PostgreSQL platform. SvelteKit is a modern web framework for building fast, dynamic applications with server-side rendering and API endpoints. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of Svelte and SvelteKit + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create SvelteKit Project + +Create a new SvelteKit project: + +```bash +npm create svelte@latest my-xata-app +cd my-xata-app +npm install +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install the PostgreSQL client: + +```bash +npm install postgres +npm install -D @types/pg +``` + +## Configure Database Connection + +Create a database configuration file: + +```typescript +// src/lib/server/db.ts +import postgres from 'postgres'; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error('DATABASE_URL environment variable is required'); +} + +export const sql = postgres(connectionString); +``` + +## Create API Endpoint + +Create an API endpoint to fetch products: + +```typescript +// src/routes/api/products/+server.ts +import { sql } from '$lib/server/db'; +import { json } from '@sveltejs/kit'; + +export async function GET() { + try { + const products = await sql`SELECT * FROM products`; + return json(products); + } catch (error) { + return json({ error: 'Failed to fetch products' }, { status: 500 }); + } +} +``` + +## Create Products Page + +Create a page to display products: + +```svelte + + + +
+

Xata E-commerce Store

+ {#if loading} +
Loading products...
+ {:else} +
+ {#each products as product} +
+

{product.name}

+

${product.price}

+ {#if product.rating} +
+ + {product.rating}/5 +
+ {/if} +
+ {/each} +
+ {/if} +
+``` + + + +## Run the Application + +Start your development server: + +```bash +npm run dev +``` + +Visit `http://localhost:5173` to see your application. + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file diff --git a/docs/quickstarts/typeorm.mdx b/docs/quickstarts/typeorm.mdx new file mode 100644 index 0000000..0e6f7e9 --- /dev/null +++ b/docs/quickstarts/typeorm.mdx @@ -0,0 +1,585 @@ +--- +title: "Connect TypeORM to Xata" +description: "Learn how to connect your TypeScript/JavaScript application using TypeORM to Xata's PostgreSQL platform. Get started with TypeORM and PostgreSQL for enterprise-grade database operations." +--- + +# Connect TypeORM to Xata + +Learn how to use TypeORM with Xata's PostgreSQL platform. TypeORM is a powerful TypeScript ORM that supports both Active Record and Data Mapper patterns, providing excellent database abstraction and type safety. + +## Prerequisites + +- Xata account and project setup +- Node.js 18+ installed +- Basic knowledge of TypeScript and TypeORM + +## Setup Xata Database + +First, set up your Xata database with the common e-commerce dataset: + +1. Create a project and branch in the [Xata console](https://console.xata.io) +2. Navigate to the **Queries** tab in your branch +3. Run the following SQL commands to create the initial schema: + +```sql +CREATE TABLE products ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + price NUMERIC(7,2) NOT NULL, + rating INTEGER +); + +CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE order_items ( + order_id INTEGER REFERENCES orders(id), + product_id INTEGER REFERENCES products(id), + qty INTEGER NOT NULL, + PRIMARY KEY (order_id, product_id) +); +``` + +## Create Project + +Create a new Node.js project: + +```bash +mkdir my-xata-app +cd my-xata-app +npm init -y +``` + +## Initialize Xata Project + +Initialize your Xata project configuration: + +```bash +xata init +``` + +This will create a `.xata` directory with your project configuration. + +## Store Your Credentials + +Create a `.env` file in your project root to store your Xata connection string: + +```bash +# .env +DATABASE_URL="postgresql://username:password@host:port/database" +``` + +Get your connection string from the Xata console or CLI: + +```bash +xata branch url +``` + +**Important:** Never commit your `.env` file to version control. Add it to your `.gitignore` file. + +## Install Dependencies + +Install TypeORM and PostgreSQL driver: + +```bash +npm install typeorm reflect-metadata pg +npm install -D typescript @types/node @types/pg +``` + +## Configure TypeScript + +Create a `tsconfig.json` file: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +## Create Database Configuration + +Create a database configuration file: + +```typescript +// config/database.ts +import { DataSource } from 'typeorm' +import { config } from 'dotenv' +import { Product } from '../entities/Product' +import { Order } from '../entities/Order' +import { OrderItem } from '../entities/OrderItem' + +config() + +export const AppDataSource = new DataSource({ + type: 'postgres', + url: process.env.DATABASE_URL, + synchronize: false, // Set to false in production + logging: true, + entities: [Product, Order, OrderItem], + subscribers: [], + migrations: [], +}) + +export const initializeDatabase = async () => { + try { + await AppDataSource.initialize() + console.log('Database connection established') + } catch (error) { + console.error('Error connecting to database:', error) + throw error + } +} +``` + +## Create Entities + +Create your TypeORM entities: + +```typescript +// entities/Product.ts +import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm' +import { OrderItem } from './OrderItem' + +@Entity('products') +export class Product { + @PrimaryGeneratedColumn() + id: number + + @Column({ type: 'text', nullable: false }) + name: string + + @Column({ type: 'decimal', precision: 7, scale: 2, nullable: false }) + price: number + + @Column({ type: 'integer', nullable: true }) + rating: number + + @OneToMany(() => OrderItem, orderItem => orderItem.product) + orderItems: OrderItem[] +} +``` + +```typescript +// entities/Order.ts +import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm' +import { OrderItem } from './OrderItem' + +@Entity('orders') +export class Order { + @PrimaryGeneratedColumn() + id: number + + @CreateDateColumn({ type: 'timestamp with time zone' }) + created_at: Date + + @OneToMany(() => OrderItem, orderItem => orderItem.order) + orderItems: OrderItem[] +} +``` + +```typescript +// entities/OrderItem.ts +import { Entity, PrimaryColumn, Column, ManyToOne, JoinColumn } from 'typeorm' +import { Product } from './Product' +import { Order } from './Order' + +@Entity('order_items') +export class OrderItem { + @PrimaryColumn({ type: 'integer' }) + order_id: number + + @PrimaryColumn({ type: 'integer' }) + product_id: number + + @Column({ type: 'integer', nullable: false }) + qty: number + + @ManyToOne(() => Order, order => order.orderItems) + @JoinColumn({ name: 'order_id' }) + order: Order + + @ManyToOne(() => Product, product => product.orderItems) + @JoinColumn({ name: 'product_id' }) + product: Product +} +``` + +## Create Services + +Create service layer for database operations: + +```typescript +// services/ProductService.ts +import { Repository } from 'typeorm' +import { AppDataSource } from '../config/database' +import { Product } from '../entities/Product' + +export class ProductService { + private productRepository: Repository + + constructor() { + this.productRepository = AppDataSource.getRepository(Product) + } + + async getAllProducts(): Promise { + return await this.productRepository.find() + } + + async getProductById(id: number): Promise { + return await this.productRepository.findOne({ where: { id } }) + } + + async createProduct(name: string, price: number, rating?: number): Promise { + const product = this.productRepository.create({ + name, + price, + rating + }) + return await this.productRepository.save(product) + } + + async updateProduct(id: number, updates: Partial): Promise { + await this.productRepository.update(id, updates) + return await this.getProductById(id) + } + + async deleteProduct(id: number): Promise { + const result = await this.productRepository.delete(id) + return result.affected !== 0 + } + + async getProductsByRating(rating: number): Promise { + return await this.productRepository.find({ where: { rating } }) + } + + async getProductsByPriceRange(minPrice: number, maxPrice: number): Promise { + return await this.productRepository + .createQueryBuilder('product') + .where('product.price >= :minPrice', { minPrice }) + .andWhere('product.price <= :maxPrice', { maxPrice }) + .getMany() + } +} +``` + +```typescript +// services/OrderService.ts +import { Repository } from 'typeorm' +import { AppDataSource } from '../config/database' +import { Order } from '../entities/Order' +import { OrderItem } from '../entities/OrderItem' +import { Product } from '../entities/Product' + +export class OrderService { + private orderRepository: Repository + private orderItemRepository: Repository + + constructor() { + this.orderRepository = AppDataSource.getRepository(Order) + this.orderItemRepository = AppDataSource.getRepository(OrderItem) + } + + async getAllOrders(): Promise { + return await this.orderRepository.find({ + relations: ['orderItems', 'orderItems.product'] + }) + } + + async getOrderById(id: number): Promise { + return await this.orderRepository.findOne({ + where: { id }, + relations: ['orderItems', 'orderItems.product'] + }) + } + + async createOrder(): Promise { + const order = this.orderRepository.create() + return await this.orderRepository.save(order) + } + + async addItemToOrder(orderId: number, productId: number, qty: number): Promise { + const order = await this.getOrderById(orderId) + const product = await AppDataSource.getRepository(Product).findOne({ where: { id: productId } }) + + if (!order || !product) { + return null + } + + const orderItem = this.orderItemRepository.create({ + order_id: orderId, + product_id: productId, + qty + }) + + return await this.orderItemRepository.save(orderItem) + } + + async getOrdersWithProductDetails(): Promise { + return await this.orderRepository + .createQueryBuilder('order') + .select([ + 'order.id as order_id', + 'order.created_at as created_at', + 'product.name as product_name', + 'product.price as price', + 'orderItem.qty as qty', + '(product.price * orderItem.qty) as total' + ]) + .leftJoin('order.orderItems', 'orderItem') + .leftJoin('orderItem.product', 'product') + .getRawMany() + } +} +``` + +## Create Sample Application + +Create a simple application to demonstrate the functionality: + +```typescript +// app.ts +import 'reflect-metadata' +import { initializeDatabase } from './config/database' +import { ProductService } from './services/ProductService' +import { OrderService } from './services/OrderService' + +async function main() { + try { + // Initialize database connection + await initializeDatabase() + + const productService = new ProductService() + const orderService = new OrderService() + + // Create sample products + console.log('Creating sample products...') + const products = await Promise.all([ + productService.createProduct('Wireless Headphones', 99.99, 5), + productService.createProduct('Smartphone', 699.99, 4), + productService.createProduct('Laptop', 1299.99, 5), + productService.createProduct('Coffee Maker', 89.99, 4) + ]) + + console.log(`Created ${products.length} products`) + + // Display all products + console.log('\nAll products:') + const allProducts = await productService.getAllProducts() + for (const product of allProducts) { + console.log(`- ${product.name}: $${product.price} (Rating: ${product.rating}/5)`) + } + + // Get products by rating + console.log('\nTop-rated products (5 stars):') + const topProducts = await productService.getProductsByRating(5) + for (const product of topProducts) { + console.log(`- ${product.name}: $${product.price}`) + } + + // Create an order with items + console.log('\nCreating an order...') + const order = await orderService.createOrder() + console.log(`Created order #${order.id}`) + + // Add items to order + await orderService.addItemToOrder(order.id, products[0].id, 2) // 2 headphones + await orderService.addItemToOrder(order.id, products[2].id, 1) // 1 laptop + + // Get order details with products + console.log('\nOrder details:') + const orderDetails = await orderService.getOrdersWithProductDetails() + for (const detail of orderDetails) { + console.log(`Order #${detail.order_id}: ${detail.product_name} x${detail.qty} = $${detail.total}`) + } + + // Update a product + console.log('\nUpdating product...') + const updatedProduct = await productService.updateProduct(products[0].id, { + name: 'Premium Wireless Headphones', + price: 129.99 + }) + if (updatedProduct) { + console.log(`Updated: ${updatedProduct.name} - $${updatedProduct.price}`) + } + + } catch (error) { + console.error('Error:', error) + } finally { + await AppDataSource.destroy() + } +} + +main() +``` + +## Create Package Scripts + +Update your `package.json` scripts: + +```json +{ + "scripts": { + "build": "tsc", + "start": "node dist/app.js", + "dev": "ts-node app.ts", + "typeorm": "typeorm-ts-node-commonjs" + } +} +``` + +## Run the Application + +Install dependencies and run the application: + +```bash +npm install ts-node dotenv +npm run dev +``` + +## Create Express API (Optional) + +For a web API, you can integrate with Express: + +```typescript +// api.ts +import 'reflect-metadata' +import express from 'express' +import { initializeDatabase } from './config/database' +import { ProductService } from './services/ProductService' +import { OrderService } from './services/OrderService' + +const app = express() +app.use(express.json()) + +let productService: ProductService +let orderService: OrderService + +// Initialize database and services +initializeDatabase().then(() => { + productService = new ProductService() + orderService = new OrderService() +}) + +// Product routes +app.get('/api/products', async (req, res) => { + try { + const products = await productService.getAllProducts() + res.json(products) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch products' }) + } +}) + +app.get('/api/products/:id', async (req, res) => { + try { + const product = await productService.getProductById(parseInt(req.params.id)) + if (!product) { + return res.status(404).json({ error: 'Product not found' }) + } + res.json(product) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch product' }) + } +}) + +app.post('/api/products', async (req, res) => { + try { + const { name, price, rating } = req.body + const product = await productService.createProduct(name, price, rating) + res.status(201).json(product) + } catch (error) { + res.status(500).json({ error: 'Failed to create product' }) + } +}) + +app.put('/api/products/:id', async (req, res) => { + try { + const product = await productService.updateProduct(parseInt(req.params.id), req.body) + if (!product) { + return res.status(404).json({ error: 'Product not found' }) + } + res.json(product) + } catch (error) { + res.status(500).json({ error: 'Failed to update product' }) + } +}) + +app.delete('/api/products/:id', async (req, res) => { + try { + const success = await productService.deleteProduct(parseInt(req.params.id)) + if (!success) { + return res.status(404).json({ error: 'Product not found' }) + } + res.status(204).send() + } catch (error) { + res.status(500).json({ error: 'Failed to delete product' }) + } +}) + +app.get('/api/products/rating/:rating', async (req, res) => { + try { + const products = await productService.getProductsByRating(parseInt(req.params.rating)) + res.json(products) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch products' }) + } +}) + +// Order routes +app.get('/api/orders', async (req, res) => { + try { + const orders = await orderService.getAllOrders() + res.json(orders) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch orders' }) + } +}) + +app.get('/api/orders/:id', async (req, res) => { + try { + const order = await orderService.getOrderById(parseInt(req.params.id)) + if (!order) { + return res.status(404).json({ error: 'Order not found' }) + } + res.json(order) + } catch (error) { + res.status(500).json({ error: 'Failed to fetch order' }) + } +}) + +const PORT = process.env.PORT || 3000 +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`) +}) +``` + +To run the Express API: + +```bash +npm install express +npm install -D @types/express +npx ts-node api.ts +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Join our [Discord community](https://discord.gg/xata) for help and support \ No newline at end of file From 8b9c1d6d92ed6b1439f68023535ad6448c39d33e Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Tue, 1 Jul 2025 15:46:14 -0400 Subject: [PATCH 02/13] Alphabetize quickstart routes in config.json --- docs/config.json | 98 ++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/config.json b/docs/config.json index a15c9e3..0445438 100644 --- a/docs/config.json +++ b/docs/config.json @@ -10,35 +10,50 @@ "href": "/quickstarts", "file": "docs/quickstarts/quickstarts.mdx", "items": [ + { + "title": "Astro", + "href": "/quickstarts/astro", + "file": "docs/quickstarts/astro.mdx" + }, + { + "title": "Django", + "href": "/quickstarts/django", + "file": "docs/quickstarts/django.mdx" + }, { "title": "Drizzle", "href": "/quickstarts/drizzle", "file": "docs/quickstarts/drizzle.mdx" }, { - "title": "Next.js", - "href": "/quickstarts/nextjs", - "file": "docs/quickstarts/nextjs.mdx" + "title": "Go", + "href": "/quickstarts/go", + "file": "docs/quickstarts/go.mdx" }, { - "title": "Nuxt", - "href": "/quickstarts/nuxt", - "file": "docs/quickstarts/nuxt.mdx" + "title": "GORM", + "href": "/quickstarts/gorm", + "file": "docs/quickstarts/gorm.mdx" }, { - "title": "Astro", - "href": "/quickstarts/astro", - "file": "docs/quickstarts/astro.mdx" + "title": "Java", + "href": "/quickstarts/java", + "file": "docs/quickstarts/java.mdx" }, { - "title": "SvelteKit", - "href": "/quickstarts/svelte", - "file": "docs/quickstarts/svelte.mdx" + "title": "JPA", + "href": "/quickstarts/jpa", + "file": "docs/quickstarts/jpa.mdx" }, { - "title": "Remix", - "href": "/quickstarts/remix", - "file": "docs/quickstarts/remix.mdx" + "title": "Laravel", + "href": "/quickstarts/laravel", + "file": "docs/quickstarts/laravel.mdx" + }, + { + "title": "Next.js", + "href": "/quickstarts/nextjs", + "file": "docs/quickstarts/nextjs.mdx" }, { "title": "Node.js", @@ -46,9 +61,19 @@ "file": "docs/quickstarts/nodejs.mdx" }, { - "title": "Django", - "href": "/quickstarts/django", - "file": "docs/quickstarts/django.mdx" + "title": "Nuxt", + "href": "/quickstarts/nuxt", + "file": "docs/quickstarts/nuxt.mdx" + }, + { + "title": "Prisma", + "href": "/quickstarts/prisma", + "file": "docs/quickstarts/prisma.mdx" + }, + { + "title": "Python", + "href": "/quickstarts/python", + "file": "docs/quickstarts/python.mdx" }, { "title": "Rails", @@ -56,54 +81,29 @@ "file": "docs/quickstarts/rails.mdx" }, { - "title": "Go", - "href": "/quickstarts/go", - "file": "docs/quickstarts/go.mdx" + "title": "Remix", + "href": "/quickstarts/remix", + "file": "docs/quickstarts/remix.mdx" }, { "title": "Rust", "href": "/quickstarts/rust", "file": "docs/quickstarts/rust.mdx" }, - { - "title": "Laravel", - "href": "/quickstarts/laravel", - "file": "docs/quickstarts/laravel.mdx" - }, - { - "title": "Java", - "href": "/quickstarts/java", - "file": "docs/quickstarts/java.mdx" - }, - { - "title": "Python", - "href": "/quickstarts/python", - "file": "docs/quickstarts/python.mdx" - }, { "title": "SQLAlchemy", "href": "/quickstarts/sqlalchemy", "file": "docs/quickstarts/sqlalchemy.mdx" }, { - "title": "Prisma", - "href": "/quickstarts/prisma", - "file": "docs/quickstarts/prisma.mdx" + "title": "SvelteKit", + "href": "/quickstarts/svelte", + "file": "docs/quickstarts/svelte.mdx" }, { "title": "TypeORM", "href": "/quickstarts/typeorm", "file": "docs/quickstarts/typeorm.mdx" - }, - { - "title": "GORM", - "href": "/quickstarts/gorm", - "file": "docs/quickstarts/gorm.mdx" - }, - { - "title": "JPA", - "href": "/quickstarts/jpa", - "file": "docs/quickstarts/jpa.mdx" } ] }, From bfd3fd5755b862e54978c753e618422eee87842f Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 13:54:14 -0400 Subject: [PATCH 03/13] Add overview landing page with Xata logo ASCII art and comprehensive navigation --- docs/config.json | 5 + docs/index.mdx | 240 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 docs/index.mdx diff --git a/docs/config.json b/docs/config.json index 0445438..6c9f787 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1,5 +1,10 @@ { "routes": [ + { + "title": "Overview", + "href": "/", + "file": "docs/index.mdx" + }, { "title": "Getting started", "href": "/getting-started", diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..cd0804b --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,240 @@ +--- +title: "Xata Documentation" +description: "Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization. Build faster with our serverless database." +--- + +# Xata Documentation + +``` + ++++ +++++ + +++++++++ ++++++++++ + +++++++++++++ +++++++++++++ + +++++++++++++++= +++++++++++++++ + +++++++++++++++=== +=++++++++++++++++ + +++++++++++++======= =====++++++++++++++++ + +++++++++++++========== ==========+++++++++++++ + +++++++++++++============= =============+++++++++++++ + +++++++++++================= ================+++++++++++++ +++++++++++===================== ==================++++++++++++ +++++++++++====================== ======================++++++++++ +++++++++========================== ========================+=++++++++ +++++++++============================ ============================+++++++++ +++++++================================ ==============================+=+++++++ +++++++================================== =================================+++++++ +++++++=================================== ===================================++++++ ++++++===================================== =====================================+++++ +++++======================================= =======================================++++ + +++======================================== ========================================++++ + +++========================================= =========================================+++ + +=========================================== ===========================================+ + =+=========================================== ===========================================+ + ============================================ ============================================ + ============================================ =========================================== + =========================================== =========================================== + ========================================== =========================================+ + ======================================== ======================================== + ====================================== ======================================= + ===================================== ===================================== + === =================================== =================================== ==== + ======= ================================= ================================= ====== + ========= =============================== =============================== ========= + ============ ============================ ============================ ============ + ============== ========================== =========================== ============== + ================= ======================= ======================== ================= + =================== ====================== ====================== =================== + ===================== =================== =================== ===================== + ======================== ================ ================ ======================= + +======================== ============= ============= ========================= + ========================== ========== ========== ========================== + ++========================== ====== ======= ===========================+ + ++++========================== === === ==========================++++ + +++++========================== ==========================++++ + +++++++======================== ==========================+++++ + +++++++====================== ======================+++++++ + +++++++++=================== ===================++++++++ + ++++++++++=============== ===============+++++++++ + ++++++++++============ ============++++++++++ + +++++++++++======== =========++++++++++ + ++++++++++++==== ====++++++++++++ + +++++++++++++ +++++++++++++ + +++++++++ +++++++++ + ++++++ +++++ +``` + +**Xata** is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. We combine the power of PostgreSQL with developer-friendly features like **instant branching**, **zero-downtime schema changes**, and **built-in data anonymization**. + +## Why Xata? + +| Feature | Description | Benefit | +|---------|-------------|---------| +| **🚀 Instant Branching** | Create database branches in seconds, not minutes | Develop and test with production-like data safely | +| **⚡ Zero-Downtime Schema Changes** | Modify your database schema without service interruption | Deploy schema changes with confidence | +| **🔒 Built-in Data Anonymization** | Automatically anonymize sensitive data for development | Use real data safely in non-production environments | +| **🌐 Global PostgreSQL** | Fully managed PostgreSQL with global distribution | Focus on your application, not database management | +| **🔧 Developer-First** | CLI tools, branching workflows, and modern integrations | Work the way you want with familiar tools | + +## What We Excel At + +### 🏗️ **Modern Development Workflows** +- **Database branching** for feature development and testing +- **Schema evolution** without downtime or data loss +- **CI/CD integration** with GitHub Actions and automation +- **Multi-environment management** (dev, staging, production) + +### 🔒 **Data Privacy & Compliance** +- **Automatic data anonymization** for development environments +- **GDPR compliance** with built-in data protection features +- **Audit trails** and data lineage tracking +- **Secure data handling** with encryption at rest and in transit + +### ⚡ **Performance & Scalability** +- **Serverless PostgreSQL** that scales automatically +- **Global distribution** for low-latency access +- **Connection pooling** and query optimization +- **Built-in caching** and performance monitoring + +### 🛠️ **Developer Experience** +- **Native PostgreSQL** compatibility +- **Comprehensive CLI** for database management +- **Framework integrations** for popular languages and ORMs +- **Real-time collaboration** features + +## Quick Start + +Get up and running with Xata in minutes: + +1. **[Create your first project](/getting-started)** - Set up a Xata account and create your first database +2. **[Choose your framework](/quickstarts)** - Connect your favorite language or framework +3. **[Explore branching](/core-concepts/branching)** - Learn how database branching accelerates development +4. **[Deploy to production](/deployment)** - Understand our deployment models and best practices + +## Popular Integrations + +### Frontend Frameworks +| Framework | Quickstart | Description | +|-----------|------------|-------------| +| **Next.js** | [Get Started →](/quickstarts/nextjs) | Full-stack React applications | +| **Nuxt** | [Get Started →](/quickstarts/nuxt) | Vue.js applications | +| **Astro** | [Get Started →](/quickstarts/astro) | Content-focused websites | +| **SvelteKit** | [Get Started →](/quickstarts/svelte) | Svelte applications | +| **Remix** | [Get Started →](/quickstarts/remix) | Full-stack web applications | + +### Backend Frameworks +| Framework | Quickstart | Description | +|-----------|------------|-------------| +| **Node.js** | [Get Started →](/quickstarts/nodejs) | JavaScript/TypeScript backend | +| **Django** | [Get Started →](/quickstarts/django) | Python web framework | +| **Rails** | [Get Started →](/quickstarts/rails) | Ruby web framework | +| **Go** | [Get Started →](/quickstarts/go) | Go applications | +| **Rust** | [Get Started →](/quickstarts/rust) | Rust applications | + +### ORMs & Database Tools +| ORM | Quickstart | Description | +|-----|------------|-------------| +| **Drizzle** | [Get Started →](/quickstarts/drizzle) | TypeScript ORM | +| **Prisma** | [Get Started →](/quickstarts/prisma) | TypeScript ORM | +| **SQLAlchemy** | [Get Started →](/quickstarts/sqlalchemy) | Python ORM | +| **GORM** | [Get Started →](/quickstarts/gorm) | Go ORM | +| **JPA** | [Get Started →](/quickstarts/jpa) | Java ORM | + +## Core Concepts + +| Concept | Description | Learn More | +|---------|-------------|------------| +| **Database Branching** | Create instant copies of your database for development and testing | [Branching Guide →](/core-concepts/branching) | +| **Schema Changes** | Modify your database structure without downtime | [Schema Changes →](/core-concepts/schema-changes) | +| **Data Anonymization** | Automatically protect sensitive data in development | [Anonymization →](/core-concepts/anonymization) | +| **Deployment Models** | Choose the right deployment strategy for your needs | [Deployment →](/deployment) | + +## Tutorials & Guides + +| Tutorial | Description | Time | +|----------|-------------|------| +| **[Set up staging replica](/tutorials/create-staging-replica)** | Create a staging environment with production data | 10 min | +| **[Migrate to Xata](/tutorials/migrate-to-xata)** | Move your existing database to Xata | 15 min | +| **[Schema changes workflow](/tutorials/schema-change)** | Learn best practices for schema evolution | 20 min | + +## Tools & Integrations + +### Command Line Interface +Manage your Xata databases directly from the terminal: + +```bash +# Install Xata CLI +npm install -g @xata.io/cli + +# Authenticate and initialize +xata auth +xata init + +# Create and manage branches +xata branch create feature-branch +xata branch checkout main +``` + +[View all CLI commands →](/cli) + +### Automation & CI/CD +Integrate Xata into your development workflow: + +- **[GitHub Actions](/automations)** - Automate database operations +- **[Kubernetes](/automations/kubernetes-clone)** - Deploy with container orchestration +- **[Branch management](/automations/ga-pr)** - Automatic branch creation for PRs + +### REST API +Programmatically manage your Xata resources: + +- **[Organizations](/api/organizations)** - Manage teams and access +- **[Projects](/api/projects)** - Create and configure databases +- **[Branches](/api/branches)** - Branch management operations +- **[Metrics](/api/metrics)** - Performance and usage analytics + +## Use Cases + +### 🚀 **Startup & MVP Development** +- **Rapid prototyping** with instant database setup +- **Feature development** with isolated database branches +- **Team collaboration** with shared development environments +- **Production deployment** with zero-downtime schema changes + +### 🏢 **Enterprise Applications** +- **Multi-tenant architectures** with data isolation +- **Compliance requirements** with built-in data protection +- **Global distribution** for international applications +- **Audit and governance** with comprehensive logging + +### 🔬 **Data Science & Analytics** +- **Development environments** with anonymized production data +- **A/B testing** with isolated database branches +- **Data pipelines** with schema evolution support +- **Real-time analytics** with PostgreSQL's analytical capabilities + +### 🛡️ **Security & Compliance** +- **GDPR compliance** with data anonymization +- **SOC 2 compliance** with enterprise-grade security +- **Data residency** with global deployment options +- **Access control** with fine-grained permissions + +## Get Help + +- **[Discord Community](https://discord.gg/xata)** - Join 10,000+ developers +- **[GitHub Discussions](https://github.com/xataio/xata/discussions)** - Ask questions and share solutions +- **[Status Page](https://status.xata.io)** - Check service status +- **[Support](https://xata.io/support)** - Enterprise support and consulting + +## What's Next? + +Ready to get started? Choose your path: + +| I want to... | Start Here | +|--------------|------------| +| **Learn the basics** | [Getting Started Guide →](/getting-started) | +| **Connect my app** | [Quickstarts →](/quickstarts) | +| **Understand branching** | [Core Concepts →](/core-concepts) | +| **Deploy to production** | [Deployment Guide →](/deployment) | +| **Explore the API** | [API Reference →](/api) | +| **Join the community** | [Discord →](https://discord.gg/xata) | + +--- + +*Built with ❤️ by the Xata team* \ No newline at end of file From 071e3a38d434bd24e0b7f03881c584c44055d3d1 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 14:45:42 -0400 Subject: [PATCH 04/13] adding homepage --- docs/assets/images/intro_xata.png | Bin 0 -> 24474 bytes docs/config.json | 5 ++ docs/index.mdx | 94 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 docs/assets/images/intro_xata.png create mode 100644 docs/index.mdx diff --git a/docs/assets/images/intro_xata.png b/docs/assets/images/intro_xata.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4fd28dad34d914691c8e6b09d32bac8720b85c GIT binary patch literal 24474 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 S4xmc^p1QJ*QjLOD@c#qVQK#Gh literal 0 HcmV?d00001 diff --git a/docs/config.json b/docs/config.json index 0445438..6c9f787 100644 --- a/docs/config.json +++ b/docs/config.json @@ -1,5 +1,10 @@ { "routes": [ + { + "title": "Overview", + "href": "/", + "file": "docs/index.mdx" + }, { "title": "Getting started", "href": "/getting-started", diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000..2d587b0 --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,94 @@ +--- +title: "Xata Documentation" +description: "Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization." +--- + +# Xata Documentation + +Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. + +Here's a quick introductary video to learn more: + +[![Introduction to Xata](assets/images/intro_xata.png)](https://youtu.be/7P5kyzEfAdc?si=WvSdBDBp95vJM91U) + +## Why Xata? + +| Feature | Description | Benefit | Learn More | +|---------|-------------|---------|------------| +| **🚀 Instant Branching** | Create database branches in seconds, not minutes | Develop and test with production-like data safely | [Branching Guide →](/core-concepts/branching) | +| **⚡ Zero-Downtime Schema Changes** | Modify your database schema without service interruption | Deploy schema changes with confidence | [Schema Changes →](/core-concepts/schema-changes) | +| **🔒 Built-in Data Anonymization** | Automatically anonymize sensitive data for development | Use real data safely in non-production environments | [Anonymization →](/core-concepts/anonymization) | +| **🏗️ Deployment Flexibility** | Choose between SaaS or bring-your-own-cloud (BYOC) | Deploy where you need - managed service or your infrastructure | [Deployment →](/deployment) | + +## Get Started + +Get up and running with Xata in minutes: + +1. **[Create your first project](/getting-started)** - Set up a Xata account and create your first database +2. **[Choose your framework](/quickstarts)** - Connect your favorite language or framework + +### Popular Quickstarts + +| Framework/ORM | Quickstart | Description | +|---------------|------------|-------------| +| **Next.js** | [Get Started →](/quickstarts/nextjs) | Full-stack React applications | +| **Drizzle** | [Get Started →](/quickstarts/drizzle) | TypeScript ORM with type safety | +| **SQLAlchemy** | [Get Started →](/quickstarts/sqlalchemy) | Python SQL toolkit and ORM | +| **Django** | [Get Started →](/quickstarts/django) | Python web framework | + +[View all quickstarts →](/quickstarts) + +## Tutorials & Guides + +| Tutorial | Description | Time | +|----------|-------------|------| +| **[Set up staging replica](/tutorials/create-staging-replica)** | Create a staging environment with production data | 10 min | +| **[Migrate to Xata](/tutorials/migrate-to-xata)** | Move your existing database to Xata | 15 min | +| **[Schema changes workflow](/tutorials/schema-change)** | Learn best practices for schema evolution | 20 min | + +## Tools & Integrations + +### Command Line Interface +Manage your Xata databases directly from the terminal: + +```bash +# Install Xata CLI +npm install -g @xata.io/cli + +# Authenticate and initialize +xata auth +xata init + +# Create and manage branches +xata branch create feature-branch +xata branch checkout main +``` + +[View all CLI commands →](/cli) + +### Automation & CI/CD +Integrate Xata into your development workflow: + +- **[GitHub Actions](/automations/ga-pr)** - Create database branches for pull requests +- **[Kubernetes](/automations/kubernetes-clone)** - Deploy with container orchestration + +[View all automation workflows →](/automations) + +### REST API +Programmatically manage your Xata resources: + +- **[Organizations](/api/organizations)** - Manage teams and access +- **[Projects](/api/projects)** - Create and configure databases +- **[Branches](/api/branches)** - Branch management operations +- **[Metrics](/api/metrics)** - Performance and usage analytics + +## Get Help + +- **[Discord Community](https://discord.gg/xata)** - Join 10,000+ developers +- **[GitHub Discussions](https://github.com/xataio/xata/discussions)** - Ask questions and share solutions +- **[Status Page](https://status.xata.io)** - Check service status +- **[Support](https://xata.io/support)** - Enterprise support and consulting + +--- + +*Built with ❤️ by the Xata team* \ No newline at end of file From dec587540a29ef3805289d8db066c437b9deffd1 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 14:52:46 -0400 Subject: [PATCH 05/13] Resolve merge conflict and update homepage --- docs/assets/images/intro_xata.png | Bin 0 -> 24474 bytes docs/index.mdx | 153 +++++------------------------- 2 files changed, 23 insertions(+), 130 deletions(-) create mode 100644 docs/assets/images/intro_xata.png diff --git a/docs/assets/images/intro_xata.png b/docs/assets/images/intro_xata.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4fd28dad34d914691c8e6b09d32bac8720b85c GIT binary patch literal 24474 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 S4xmc^p1QJ*QjLOD@c#qVQK#Gh literal 0 HcmV?d00001 diff --git a/docs/index.mdx b/docs/index.mdx index cd0804b..ed49b3e 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,150 +1,42 @@ --- title: "Xata Documentation" -description: "Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization. Build faster with our serverless database." +description: "Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization." --- # Xata Documentation -``` - ++++ +++++ - +++++++++ ++++++++++ - +++++++++++++ +++++++++++++ - +++++++++++++++= +++++++++++++++ - +++++++++++++++=== +=++++++++++++++++ - +++++++++++++======= =====++++++++++++++++ - +++++++++++++========== ==========+++++++++++++ - +++++++++++++============= =============+++++++++++++ - +++++++++++================= ================+++++++++++++ -++++++++++===================== ==================++++++++++++ -++++++++++====================== ======================++++++++++ -++++++++========================== ========================+=++++++++ -++++++++============================ ============================+++++++++ -++++++================================ ==============================+=+++++++ -++++++================================== =================================+++++++ -++++++=================================== ===================================++++++ -+++++===================================== =====================================+++++ -++++======================================= =======================================++++ - +++======================================== ========================================++++ - +++========================================= =========================================+++ - +=========================================== ===========================================+ - =+=========================================== ===========================================+ - ============================================ ============================================ - ============================================ =========================================== - =========================================== =========================================== - ========================================== =========================================+ - ======================================== ======================================== - ====================================== ======================================= - ===================================== ===================================== - === =================================== =================================== ==== - ======= ================================= ================================= ====== - ========= =============================== =============================== ========= - ============ ============================ ============================ ============ - ============== ========================== =========================== ============== - ================= ======================= ======================== ================= - =================== ====================== ====================== =================== - ===================== =================== =================== ===================== - ======================== ================ ================ ======================= - +======================== ============= ============= ========================= - ========================== ========== ========== ========================== - ++========================== ====== ======= ===========================+ - ++++========================== === === ==========================++++ - +++++========================== ==========================++++ - +++++++======================== ==========================+++++ - +++++++====================== ======================+++++++ - +++++++++=================== ===================++++++++ - ++++++++++=============== ===============+++++++++ - ++++++++++============ ============++++++++++ - +++++++++++======== =========++++++++++ - ++++++++++++==== ====++++++++++++ - +++++++++++++ +++++++++++++ - +++++++++ +++++++++ - ++++++ +++++ -``` +Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. + +Here's a quick introductary video to learn more: -**Xata** is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. We combine the power of PostgreSQL with developer-friendly features like **instant branching**, **zero-downtime schema changes**, and **built-in data anonymization**. +[![Introduction to Xata](assets/images/intro_xata.png)](https://youtu.be/7P5kyzEfAdc?si=WvSdBDBp95vJM91U) ## Why Xata? -| Feature | Description | Benefit | -|---------|-------------|---------| -| **🚀 Instant Branching** | Create database branches in seconds, not minutes | Develop and test with production-like data safely | -| **⚡ Zero-Downtime Schema Changes** | Modify your database schema without service interruption | Deploy schema changes with confidence | -| **🔒 Built-in Data Anonymization** | Automatically anonymize sensitive data for development | Use real data safely in non-production environments | -| **🌐 Global PostgreSQL** | Fully managed PostgreSQL with global distribution | Focus on your application, not database management | -| **🔧 Developer-First** | CLI tools, branching workflows, and modern integrations | Work the way you want with familiar tools | - -## What We Excel At - -### 🏗️ **Modern Development Workflows** -- **Database branching** for feature development and testing -- **Schema evolution** without downtime or data loss -- **CI/CD integration** with GitHub Actions and automation -- **Multi-environment management** (dev, staging, production) - -### 🔒 **Data Privacy & Compliance** -- **Automatic data anonymization** for development environments -- **GDPR compliance** with built-in data protection features -- **Audit trails** and data lineage tracking -- **Secure data handling** with encryption at rest and in transit - -### ⚡ **Performance & Scalability** -- **Serverless PostgreSQL** that scales automatically -- **Global distribution** for low-latency access -- **Connection pooling** and query optimization -- **Built-in caching** and performance monitoring - -### 🛠️ **Developer Experience** -- **Native PostgreSQL** compatibility -- **Comprehensive CLI** for database management -- **Framework integrations** for popular languages and ORMs -- **Real-time collaboration** features - -## Quick Start +| Feature | Description | Benefit | Learn More | +|---------|-------------|---------|------------| +| **🚀 Instant Branching** | Create database branches in seconds, not minutes | Develop and test with production-like data safely | [Branching Guide →](/core-concepts/branching) | +| **⚡ Zero-Downtime Schema Changes** | Modify your database schema without service interruption | Deploy schema changes with confidence | [Schema Changes →](/core-concepts/schema-changes) | +| **🔒 Built-in Data Anonymization** | Automatically anonymize sensitive data for development | Use real data safely in non-production environments | [Anonymization →](/core-concepts/anonymization) | +| **🏗️ Deployment Flexibility** | Choose between SaaS or bring-your-own-cloud (BYOC) | Deploy where you need - managed service or your infrastructure | [Deployment →](/deployment) | + +## Get Started Get up and running with Xata in minutes: 1. **[Create your first project](/getting-started)** - Set up a Xata account and create your first database 2. **[Choose your framework](/quickstarts)** - Connect your favorite language or framework -3. **[Explore branching](/core-concepts/branching)** - Learn how database branching accelerates development -4. **[Deploy to production](/deployment)** - Understand our deployment models and best practices -## Popular Integrations +### Popular Quickstarts -### Frontend Frameworks -| Framework | Quickstart | Description | -|-----------|------------|-------------| +| Framework/ORM | Quickstart | Description | +|---------------|------------|-------------| | **Next.js** | [Get Started →](/quickstarts/nextjs) | Full-stack React applications | -| **Nuxt** | [Get Started →](/quickstarts/nuxt) | Vue.js applications | -| **Astro** | [Get Started →](/quickstarts/astro) | Content-focused websites | -| **SvelteKit** | [Get Started →](/quickstarts/svelte) | Svelte applications | -| **Remix** | [Get Started →](/quickstarts/remix) | Full-stack web applications | - -### Backend Frameworks -| Framework | Quickstart | Description | -|-----------|------------|-------------| -| **Node.js** | [Get Started →](/quickstarts/nodejs) | JavaScript/TypeScript backend | +| **Drizzle** | [Get Started →](/quickstarts/drizzle) | TypeScript ORM with type safety | +| **SQLAlchemy** | [Get Started →](/quickstarts/sqlalchemy) | Python SQL toolkit and ORM | | **Django** | [Get Started →](/quickstarts/django) | Python web framework | -| **Rails** | [Get Started →](/quickstarts/rails) | Ruby web framework | -| **Go** | [Get Started →](/quickstarts/go) | Go applications | -| **Rust** | [Get Started →](/quickstarts/rust) | Rust applications | - -### ORMs & Database Tools -| ORM | Quickstart | Description | -|-----|------------|-------------| -| **Drizzle** | [Get Started →](/quickstarts/drizzle) | TypeScript ORM | -| **Prisma** | [Get Started →](/quickstarts/prisma) | TypeScript ORM | -| **SQLAlchemy** | [Get Started →](/quickstarts/sqlalchemy) | Python ORM | -| **GORM** | [Get Started →](/quickstarts/gorm) | Go ORM | -| **JPA** | [Get Started →](/quickstarts/jpa) | Java ORM | - -## Core Concepts - -| Concept | Description | Learn More | -|---------|-------------|------------| -| **Database Branching** | Create instant copies of your database for development and testing | [Branching Guide →](/core-concepts/branching) | -| **Schema Changes** | Modify your database structure without downtime | [Schema Changes →](/core-concepts/schema-changes) | -| **Data Anonymization** | Automatically protect sensitive data in development | [Anonymization →](/core-concepts/anonymization) | -| **Deployment Models** | Choose the right deployment strategy for your needs | [Deployment →](/deployment) | + +[View all quickstarts →](/quickstarts) ## Tutorials & Guides @@ -177,9 +69,10 @@ xata branch checkout main ### Automation & CI/CD Integrate Xata into your development workflow: -- **[GitHub Actions](/automations)** - Automate database operations +- **[GitHub Actions](/automations/ga-pr)** - Create database branches for pull requests - **[Kubernetes](/automations/kubernetes-clone)** - Deploy with container orchestration -- **[Branch management](/automations/ga-pr)** - Automatic branch creation for PRs + +[View all automation workflows →](/automations) ### REST API Programmatically manage your Xata resources: From 521560dd40b74e4ed74c86f55514484ae92a90fc Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 14:59:11 -0400 Subject: [PATCH 06/13] trying to fix headers --- docs/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.mdx b/docs/index.mdx index 2d587b0..4004b34 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,6 +1,6 @@ --- -title: "Xata Documentation" -description: "Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization." +title: Xata Documentation +description: Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization. --- # Xata Documentation From d6b8bc7fa32e7d6c2d3cd6ebeb6f8b9d78b172e4 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 15:07:37 -0400 Subject: [PATCH 07/13] cleaning up quickstarts and overview --- docs/config.json | 2 +- docs/quickstarts/astro.mdx | 4 ---- docs/quickstarts/django.mdx | 4 ---- docs/quickstarts/drizzle.mdx | 4 ---- docs/quickstarts/go.mdx | 4 ---- docs/quickstarts/gorm.mdx | 4 ---- docs/quickstarts/java.mdx | 4 ---- docs/quickstarts/jpa.mdx | 4 ---- docs/quickstarts/laravel.mdx | 4 ---- docs/quickstarts/nextjs.mdx | 4 ---- docs/quickstarts/nodejs.mdx | 4 ---- docs/quickstarts/nuxt.mdx | 4 ---- docs/quickstarts/prisma.mdx | 4 ---- docs/quickstarts/python.mdx | 4 ---- docs/quickstarts/quickstarts.mdx | 2 -- docs/quickstarts/rails.mdx | 4 ---- docs/quickstarts/remix.mdx | 4 ---- docs/quickstarts/rust.mdx | 4 ---- docs/quickstarts/sqlalchemy.mdx | 4 ---- docs/quickstarts/svelte.mdx | 4 ---- docs/quickstarts/typeorm.mdx | 4 ---- 21 files changed, 1 insertion(+), 79 deletions(-) diff --git a/docs/config.json b/docs/config.json index 6c9f787..10e7ee2 100644 --- a/docs/config.json +++ b/docs/config.json @@ -2,7 +2,7 @@ "routes": [ { "title": "Overview", - "href": "/", + "href": "/overview", "file": "docs/index.mdx" }, { diff --git a/docs/quickstarts/astro.mdx b/docs/quickstarts/astro.mdx index 3a9a704..799f8b9 100644 --- a/docs/quickstarts/astro.mdx +++ b/docs/quickstarts/astro.mdx @@ -3,10 +3,6 @@ title: "Connect Astro to Xata" description: "Learn how to connect your Astro application to Xata's PostgreSQL platform. Get started with Astro and PostgreSQL for fast, modern web apps." --- -# Connect Astro to Xata - -Learn how to use Astro with Xata's PostgreSQL platform. Astro is a modern static site builder that supports server-side rendering and API endpoints. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/django.mdx b/docs/quickstarts/django.mdx index e69eda8..dfaf9fc 100644 --- a/docs/quickstarts/django.mdx +++ b/docs/quickstarts/django.mdx @@ -3,10 +3,6 @@ title: "Connect Django to Xata" description: "Learn how to connect your Django application to Xata's PostgreSQL platform. Get started with Django and PostgreSQL for robust web applications." --- -# Connect Django to Xata - -Learn how to use Django with Xata's PostgreSQL platform. Django is a high-level Python web framework that provides excellent ORM capabilities and rapid development features. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/drizzle.mdx b/docs/quickstarts/drizzle.mdx index 2c3d18c..3acc824 100644 --- a/docs/quickstarts/drizzle.mdx +++ b/docs/quickstarts/drizzle.mdx @@ -3,10 +3,6 @@ title: "Connect Drizzle to Xata" description: "Learn how to connect your Drizzle ORM application to Xata's PostgreSQL platform. Get started with Drizzle and PostgreSQL for type-safe database operations." --- -# Connect Drizzle to Xata - -Learn how to use Drizzle ORM with Xata's PostgreSQL platform. Drizzle provides excellent TypeScript support and type safety for your database operations. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/go.mdx b/docs/quickstarts/go.mdx index 1e340f8..0c24e7c 100644 --- a/docs/quickstarts/go.mdx +++ b/docs/quickstarts/go.mdx @@ -3,10 +3,6 @@ title: "Connect Go to Xata" description: "Learn how to connect your Go application to Xata's PostgreSQL platform. Get started with Go and PostgreSQL for high-performance backend services." --- -# Connect Go to Xata - -Learn how to use Go with Xata's PostgreSQL platform. Go provides excellent performance and concurrency features for building scalable backend services. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/gorm.mdx b/docs/quickstarts/gorm.mdx index ad40729..bfb01c3 100644 --- a/docs/quickstarts/gorm.mdx +++ b/docs/quickstarts/gorm.mdx @@ -3,10 +3,6 @@ title: "Connect GORM to Xata" description: "Learn how to connect your Go application using GORM to Xata's PostgreSQL platform. Get started with GORM and PostgreSQL for robust database operations in Go." --- -# Connect GORM to Xata - -Learn how to use GORM with Xata's PostgreSQL platform. GORM is a popular Go ORM library that provides a developer-friendly way to interact with databases, featuring auto-migrations, hooks, and associations. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/java.mdx b/docs/quickstarts/java.mdx index 694c930..1c787f7 100644 --- a/docs/quickstarts/java.mdx +++ b/docs/quickstarts/java.mdx @@ -3,10 +3,6 @@ title: "Connect Java Spring Boot to Xata" description: "Learn how to connect your Java Spring Boot application to Xata's PostgreSQL platform. Get started with Spring Boot and PostgreSQL for enterprise applications." --- -# Connect Java Spring Boot to Xata - -Learn how to use Java Spring Boot with Xata's PostgreSQL platform. Spring Boot is a powerful Java framework that simplifies the development of production-ready applications with built-in database support. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/jpa.mdx b/docs/quickstarts/jpa.mdx index 6effb49..fdc4ce2 100644 --- a/docs/quickstarts/jpa.mdx +++ b/docs/quickstarts/jpa.mdx @@ -3,10 +3,6 @@ title: "Connect JPA/Hibernate to Xata" description: "Learn how to connect your Java application using JPA/Hibernate to Xata's PostgreSQL platform. Get started with JPA and PostgreSQL for enterprise-grade database operations." --- -# Connect JPA/Hibernate to Xata - -Learn how to use JPA (Java Persistence API) with Hibernate and Xata's PostgreSQL platform. JPA is the standard Java specification for object-relational mapping, and Hibernate is the most popular implementation, providing powerful database abstraction and persistence capabilities. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/laravel.mdx b/docs/quickstarts/laravel.mdx index 22a53dd..a75f5bc 100644 --- a/docs/quickstarts/laravel.mdx +++ b/docs/quickstarts/laravel.mdx @@ -3,10 +3,6 @@ title: "Connect Laravel to Xata" description: "Learn how to connect your Laravel application to Xata's PostgreSQL platform. Get started with Laravel and PostgreSQL for robust web applications." --- -# Connect Laravel to Xata - -Learn how to use Laravel with Xata's PostgreSQL platform. Laravel is a powerful PHP web framework with an elegant ORM (Eloquent) that makes database operations intuitive and efficient. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/nextjs.mdx b/docs/quickstarts/nextjs.mdx index aca2ebf..21cd0f2 100644 --- a/docs/quickstarts/nextjs.mdx +++ b/docs/quickstarts/nextjs.mdx @@ -3,10 +3,6 @@ title: "Connect Next.js to Xata" description: "Learn how to connect your Next.js application to Xata's PostgreSQL platform. Get started with Next.js and PostgreSQL for scalable web applications." --- -# Connect Next.js to Xata - -Learn how to use Next.js with Xata's PostgreSQL platform. Next.js provides excellent React framework features with server-side rendering and API routes. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/nodejs.mdx b/docs/quickstarts/nodejs.mdx index 6faf529..a3fbf2d 100644 --- a/docs/quickstarts/nodejs.mdx +++ b/docs/quickstarts/nodejs.mdx @@ -3,10 +3,6 @@ title: "Connect Node.js to Xata" description: "Learn how to connect your Node.js application to Xata's PostgreSQL platform. Get started with Node.js and PostgreSQL for scalable backend services." --- -# Connect Node.js to Xata - -Learn how to use Node.js with Xata's PostgreSQL platform. Node.js provides excellent JavaScript runtime for building fast, scalable backend services and APIs. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/nuxt.mdx b/docs/quickstarts/nuxt.mdx index 384e866..7b7d257 100644 --- a/docs/quickstarts/nuxt.mdx +++ b/docs/quickstarts/nuxt.mdx @@ -3,10 +3,6 @@ title: "Connect Nuxt to Xata" description: "Learn how to connect your Nuxt application to Xata's PostgreSQL platform. Get started with Nuxt and PostgreSQL for universal Vue.js applications." --- -# Connect Nuxt to Xata - -Learn how to use Nuxt with Xata's PostgreSQL platform. Nuxt provides excellent Vue.js framework features with server-side rendering and API routes. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/prisma.mdx b/docs/quickstarts/prisma.mdx index 42e7d5b..64fb4ca 100644 --- a/docs/quickstarts/prisma.mdx +++ b/docs/quickstarts/prisma.mdx @@ -3,10 +3,6 @@ title: "Connect Prisma to Xata" description: "Learn how to connect your TypeScript/JavaScript application using Prisma to Xata's PostgreSQL platform. Get started with Prisma and PostgreSQL for type-safe database operations." --- -# Connect Prisma to Xata - -Learn how to use Prisma with Xata's PostgreSQL platform. Prisma is a next-generation TypeScript ORM that provides type-safe database access, auto-generated queries, and excellent developer experience. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/python.mdx b/docs/quickstarts/python.mdx index bee785d..40bf769 100644 --- a/docs/quickstarts/python.mdx +++ b/docs/quickstarts/python.mdx @@ -3,10 +3,6 @@ title: "Connect Python to Xata" description: "Learn how to connect your Python application to Xata's PostgreSQL platform using the native psycopg2 driver. Get started with Python and PostgreSQL for direct database operations." --- -# Connect Python to Xata - -Learn how to use Python with Xata's PostgreSQL platform using the native `psycopg2` driver. This approach gives you direct control over SQL queries and database operations without the abstraction of an ORM. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/quickstarts.mdx b/docs/quickstarts/quickstarts.mdx index c3ea32e..0eaa8a2 100644 --- a/docs/quickstarts/quickstarts.mdx +++ b/docs/quickstarts/quickstarts.mdx @@ -3,8 +3,6 @@ title: Quickstarts description: Get started with Xata PostgreSQL using your favorite frameworks and ORMs --- -# Quickstarts - Connect your application to Xata's PostgreSQL platform using your preferred framework or ORM. Each quickstart uses a common e-commerce dataset to demonstrate database operations, relationships, and best practices. ## Common Dataset diff --git a/docs/quickstarts/rails.mdx b/docs/quickstarts/rails.mdx index 74c64a7..8217de0 100644 --- a/docs/quickstarts/rails.mdx +++ b/docs/quickstarts/rails.mdx @@ -3,10 +3,6 @@ title: "Connect Rails to Xata" description: "Learn how to connect your Ruby on Rails application to Xata's PostgreSQL platform. Get started with Rails and PostgreSQL for rapid web development." --- -# Connect Rails to Xata - -Learn how to use Ruby on Rails with Xata's PostgreSQL platform. Rails is a web application framework that provides excellent conventions and rapid development capabilities. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/remix.mdx b/docs/quickstarts/remix.mdx index bd6d608..7810887 100644 --- a/docs/quickstarts/remix.mdx +++ b/docs/quickstarts/remix.mdx @@ -3,10 +3,6 @@ title: "Connect Remix to Xata" description: "Learn how to connect your Remix application to Xata's PostgreSQL platform. Get started with Remix and PostgreSQL for modern, full-stack web apps." --- -# Connect Remix to Xata - -Learn how to use Remix with Xata's PostgreSQL platform. Remix is a full-stack web framework for building fast, dynamic applications with server-side rendering and API endpoints. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/rust.mdx b/docs/quickstarts/rust.mdx index b120723..a65a9d1 100644 --- a/docs/quickstarts/rust.mdx +++ b/docs/quickstarts/rust.mdx @@ -3,10 +3,6 @@ title: "Connect Rust to Xata" description: "Learn how to connect your Rust application to Xata's PostgreSQL platform. Get started with Rust and PostgreSQL for high-performance, memory-safe applications." --- -# Connect Rust to Xata - -Learn how to use Rust with Xata's PostgreSQL platform. Rust provides excellent performance, memory safety, and concurrency features for building reliable backend services. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/sqlalchemy.mdx b/docs/quickstarts/sqlalchemy.mdx index 71b0ca4..d342322 100644 --- a/docs/quickstarts/sqlalchemy.mdx +++ b/docs/quickstarts/sqlalchemy.mdx @@ -3,10 +3,6 @@ title: "Connect SQLAlchemy to Xata" description: "Learn how to connect your Python application using SQLAlchemy to Xata's PostgreSQL platform. Get started with SQLAlchemy and PostgreSQL for robust data operations." --- -# Connect SQLAlchemy to Xata - -Learn how to use SQLAlchemy with Xata's PostgreSQL platform. SQLAlchemy is a powerful Python SQL toolkit and Object-Relational Mapping (ORM) library that provides a set of high-level API for database operations. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/svelte.mdx b/docs/quickstarts/svelte.mdx index 8d5c543..b06a93c 100644 --- a/docs/quickstarts/svelte.mdx +++ b/docs/quickstarts/svelte.mdx @@ -3,10 +3,6 @@ title: "Connect SvelteKit to Xata" description: "Learn how to connect your SvelteKit application to Xata's PostgreSQL platform. Get started with SvelteKit and PostgreSQL for fast, modern web apps." --- -# Connect SvelteKit to Xata - -Learn how to use SvelteKit with Xata's PostgreSQL platform. SvelteKit is a modern web framework for building fast, dynamic applications with server-side rendering and API endpoints. - ## Prerequisites - Xata account and project setup diff --git a/docs/quickstarts/typeorm.mdx b/docs/quickstarts/typeorm.mdx index 0e6f7e9..c25cdab 100644 --- a/docs/quickstarts/typeorm.mdx +++ b/docs/quickstarts/typeorm.mdx @@ -3,10 +3,6 @@ title: "Connect TypeORM to Xata" description: "Learn how to connect your TypeScript/JavaScript application using TypeORM to Xata's PostgreSQL platform. Get started with TypeORM and PostgreSQL for enterprise-grade database operations." --- -# Connect TypeORM to Xata - -Learn how to use TypeORM with Xata's PostgreSQL platform. TypeORM is a powerful TypeScript ORM that supports both Active Record and Data Mapper patterns, providing excellent database abstraction and type safety. - ## Prerequisites - Xata account and project setup From 9e2346eca08524105badb741ac993cc9abb62562 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 15:10:39 -0400 Subject: [PATCH 08/13] clean up intro --- docs/index.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/index.mdx b/docs/index.mdx index 4004b34..5146d60 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,12 +1,8 @@ --- title: Xata Documentation -description: Xata is a modern PostgreSQL platform with instant branching, zero-downtime schema changes, and built-in data anonymization. +description: Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. --- -# Xata Documentation - -Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. - Here's a quick introductary video to learn more: [![Introduction to Xata](assets/images/intro_xata.png)](https://youtu.be/7P5kyzEfAdc?si=WvSdBDBp95vJM91U) From 1a833b07ecc8ad0b340f33d1c687bb69ea33dc3f Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Wed, 2 Jul 2025 16:01:00 -0400 Subject: [PATCH 09/13] clean up and migration guides --- docs/config.json | 57 ++++++ docs/index.mdx | 29 ++- docs/migrations/alloydb.mdx | 212 +++++++++++++++++++ docs/migrations/aws-aurora.mdx | 178 ++++++++++++++++ docs/migrations/aws-rds.mdx | 152 ++++++++++++++ docs/migrations/azure.mdx | 243 ++++++++++++++++++++++ docs/migrations/digitalocean.mdx | 282 ++++++++++++++++++++++++++ docs/migrations/gcp-cloudsql.mdx | 196 ++++++++++++++++++ docs/migrations/heroku.mdx | 276 +++++++++++++++++++++++++ docs/migrations/migrations.mdx | 52 +++++ docs/migrations/mysql.mdx | 335 +++++++++++++++++++++++++++++++ docs/migrations/neon.mdx | 216 ++++++++++++++++++++ docs/migrations/self-hosted.mdx | 244 ++++++++++++++++++++++ docs/migrations/supabase.mdx | 247 +++++++++++++++++++++++ 14 files changed, 2713 insertions(+), 6 deletions(-) create mode 100644 docs/migrations/alloydb.mdx create mode 100644 docs/migrations/aws-aurora.mdx create mode 100644 docs/migrations/aws-rds.mdx create mode 100644 docs/migrations/azure.mdx create mode 100644 docs/migrations/digitalocean.mdx create mode 100644 docs/migrations/gcp-cloudsql.mdx create mode 100644 docs/migrations/heroku.mdx create mode 100644 docs/migrations/migrations.mdx create mode 100644 docs/migrations/mysql.mdx create mode 100644 docs/migrations/neon.mdx create mode 100644 docs/migrations/self-hosted.mdx create mode 100644 docs/migrations/supabase.mdx diff --git a/docs/config.json b/docs/config.json index 10e7ee2..54ac150 100644 --- a/docs/config.json +++ b/docs/config.json @@ -139,6 +139,63 @@ } ] }, + { + "title": "Migration Guides", + "href": "/migrations", + "file": "docs/migrations/migrations.mdx", + "items": [ + { + "title": "AWS RDS & Aurora", + "href": "/migrations/aws-rds", + "file": "docs/migrations/aws-rds.mdx" + }, + { + "title": "GCP Cloud SQL", + "href": "/migrations/gcp-cloudsql", + "file": "docs/migrations/gcp-cloudsql.mdx" + }, + { + "title": "Google AlloyDB", + "href": "/migrations/alloydb", + "file": "docs/migrations/alloydb.mdx" + }, + { + "title": "Azure Database", + "href": "/migrations/azure", + "file": "docs/migrations/azure.mdx" + }, + { + "title": "Neon", + "href": "/migrations/neon", + "file": "docs/migrations/neon.mdx" + }, + { + "title": "Supabase", + "href": "/migrations/supabase", + "file": "docs/migrations/supabase.mdx" + }, + { + "title": "Self-Hosted PostgreSQL", + "href": "/migrations/self-hosted", + "file": "docs/migrations/self-hosted.mdx" + }, + { + "title": "Heroku Postgres", + "href": "/migrations/heroku", + "file": "docs/migrations/heroku.mdx" + }, + { + "title": "DigitalOcean Managed Databases", + "href": "/migrations/digitalocean", + "file": "docs/migrations/digitalocean.mdx" + }, + { + "title": "MySQL", + "href": "/migrations/mysql", + "file": "docs/migrations/mysql.mdx" + } + ] + }, { "title": "Core concepts", "href": "/core-concepts", diff --git a/docs/index.mdx b/docs/index.mdx index 5146d60..70c863b 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,8 +1,9 @@ --- title: Xata Documentation -description: Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. --- +Xata is a modern PostgreSQL platform designed for developers who want to build faster and ship with confidence. Our platform is built to support your production use cases as well as staging environments and developer workflows with features like instant branching, zero-downtime schema changes, and built-in data anonymization. + Here's a quick introductary video to learn more: [![Introduction to Xata](assets/images/intro_xata.png)](https://youtu.be/7P5kyzEfAdc?si=WvSdBDBp95vJM91U) @@ -16,6 +17,8 @@ Here's a quick introductary video to learn more: | **🔒 Built-in Data Anonymization** | Automatically anonymize sensitive data for development | Use real data safely in non-production environments | [Anonymization →](/core-concepts/anonymization) | | **🏗️ Deployment Flexibility** | Choose between SaaS or bring-your-own-cloud (BYOC) | Deploy where you need - managed service or your infrastructure | [Deployment →](/deployment) | +Interested in how we compare against other Postgres solutions out there? Check out this [comparison blog series](https://xata.io/blog/neon-vs-supabase-vs-xata-postgres-branching-part-1) for more details. + ## Get Started Get up and running with Xata in minutes: @@ -36,11 +39,25 @@ Get up and running with Xata in minutes: ## Tutorials & Guides -| Tutorial | Description | Time | -|----------|-------------|------| -| **[Set up staging replica](/tutorials/create-staging-replica)** | Create a staging environment with production data | 10 min | -| **[Migrate to Xata](/tutorials/migrate-to-xata)** | Move your existing database to Xata | 15 min | -| **[Schema changes workflow](/tutorials/schema-change)** | Learn best practices for schema evolution | 20 min | +| Tutorial | Description | +|----------|-------------| +| **[Set up staging replica](/tutorials/create-staging-replica)** | Create a staging environment with production data | +| **[Migrate to Xata](/tutorials/migrate-to-xata)** | Move your existing database to Xata | +| **[Schema changes workflow](/tutorials/schema-change)** | Learn best practices for schema evolution | + +## Migration Guides + +Migrate your existing PostgreSQL databases to Xata using `xata clone`: + +| Provider | Migration Guide | Description | +|----------|----------------|-------------| +| **AWS RDS & Aurora** | [Migrate →](/migrations/aws-rds) | Amazon's managed PostgreSQL services | +| **GCP Cloud SQL** | [Migrate →](/migrations/gcp-cloudsql) | Google Cloud's managed PostgreSQL | +| **Azure Database** | [Migrate →](/migrations/azure) | Microsoft's managed PostgreSQL service | +| **Neon** | [Migrate →](/migrations/neon) | Serverless PostgreSQL with branching | +| **Supabase** | [Migrate →](/migrations/supabase) | Open source Firebase alternative | + +[View all migration guides →](/migrations) ## Tools & Integrations diff --git a/docs/migrations/alloydb.mdx b/docs/migrations/alloydb.mdx new file mode 100644 index 0000000..e2f0ded --- /dev/null +++ b/docs/migrations/alloydb.mdx @@ -0,0 +1,212 @@ +--- +title: "Migrate from Google AlloyDB to Xata" +description: "Learn how to migrate your Google AlloyDB PostgreSQL database to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- Google AlloyDB PostgreSQL cluster +- Access to Google Cloud Console +- Xata account and project setup +- Network access to your AlloyDB cluster + +## Create Snapshot User + +Connect to your AlloyDB cluster and create a dedicated user for migration: + +```sql +-- Create snapshot user +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Public IP (Less Secure) + +If your AlloyDB cluster has a public IP: + +1. **Authorize Your IP**: + - Go to your AlloyDB cluster → "Connections" → "Networking" + - Add your IP address to "Authorized networks" + - Or temporarily add `0.0.0.0/0` for migration (remove after) + +### Option 2: Private IP (Recommended) + +For better security, use private IP: + +1. **Enable Private IP**: + - Go to your AlloyDB cluster → "Connections" → "Networking" + - Enable "Private IP" + - Configure VPC peering if needed + +2. **Run Migration from GCP**: + - Use Cloud Run, Compute Engine, or Cloud Functions + - Ensure the service is in the same VPC as your AlloyDB cluster + +### Option 3: AlloyDB Auth Proxy + +Use AlloyDB Auth Proxy for secure connections: + +```bash +# Install AlloyDB Auth Proxy +curl -o alloydb-auth-proxy https://storage.googleapis.com/alloydb-auth-proxy/v1.0.0/alloydb-auth-proxy.linux.amd64 +chmod +x alloydb-auth-proxy + +# Start the proxy +./alloydb-auth-proxy --instances=your-project:your-region:your-cluster +``` + +## Get Connection String + +### Connection String Format + +``` +postgresql://xata_snapshot:your_password@your-alloydb-ip:5432/your_database +``` + +### Find Your Cluster Details + +1. Go to AlloyDB Console → Clusters +2. Click on your cluster +3. Note the **Public IP address** (if using public IP) +4. Note the **Private IP address** (if using private IP) + +### SSL Configuration + +AlloyDB requires SSL by default. Include SSL parameters in your connection string: + +``` +postgresql://xata_snapshot:your_password@your-alloydb-ip:5432/your_database?sslmode=require +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-alloydb-ip:5432/your_database?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check authorized networks + - Verify the IP address is correct + - Ensure the cluster is running + +2. **SSL Connection Required**: + - Add `?sslmode=require` to your connection string + - Or use AlloyDB Auth Proxy for secure connections + +3. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +4. **Cluster Configuration Issues**: + - Wait for the cluster restart to complete + - Verify configuration is set correctly in the console + +### Using AlloyDB Auth Proxy + +If you're having connection issues, try using AlloyDB Auth Proxy: + +```bash +# Start proxy +./alloydb-auth-proxy --instances=your-project:your-region:your-cluster + +# Use localhost in connection string +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@localhost:5432/your_database" + +# Run migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## AlloyDB-Specific Features + +### AlloyDB Omni + +If you're using AlloyDB Omni (self-hosted): + +- **Same PostgreSQL compatibility** as cloud AlloyDB +- **Follow self-hosted PostgreSQL** migration guide +- **Use local connection** instead of cloud endpoints + +### AlloyDB for PostgreSQL + +For standard AlloyDB for PostgreSQL: + +- **Full PostgreSQL compatibility** +- **Enhanced performance** features +- **Same migration process** as other PostgreSQL databases + +## Security Best Practices + +1. **Use Private IP** when possible +2. **Remove public IP access** after migration +3. **Use strong passwords** for snapshot users +4. **Limit authorized networks** to specific IPs +5. **Enable Cloud Audit Logs** to monitor access + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication \ No newline at end of file diff --git a/docs/migrations/aws-aurora.mdx b/docs/migrations/aws-aurora.mdx new file mode 100644 index 0000000..f392a8c --- /dev/null +++ b/docs/migrations/aws-aurora.mdx @@ -0,0 +1,178 @@ +--- +title: "Migrate from AWS Aurora to Xata" +description: "Learn how to migrate your AWS Aurora PostgreSQL database to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- AWS Aurora PostgreSQL cluster +- Access to AWS Console and RDS parameter groups +- Xata account and project setup +- Network access to your Aurora cluster + +## Create Snapshot User + +Connect to your Aurora cluster and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Public Endpoint (Less Secure) + +If your Aurora cluster has a public endpoint: + +1. **Whitelist Your IP**: + - Go to your Aurora cluster → Security Groups + - Add a rule allowing PostgreSQL (port 5432) from your IP address + - Or temporarily allow from anywhere (0.0.0.0/0) for migration + +### Option 2: VPC Access (Recommended) + +For better security, run the migration from within AWS: + +1. **EC2 Instance in Same VPC**: + - Launch an EC2 instance in the same VPC as your Aurora cluster + - Install Xata CLI on the EC2 instance + - Run the migration from there + +2. **GitHub Actions with AWS Credentials**: + - Use GitHub Actions with AWS credentials + - Configure the action to run in your VPC + - Use the private endpoint for connection + +## Get Connection String + +### Aurora Connection String Format + +``` +postgresql://xata_snapshot:your_password@your-aurora-cluster-endpoint:5432/your_database +``` + +You can find the endpoint in the AWS RDS Console under your cluster details. + +### Aurora-Specific Considerations + +- **Use the cluster endpoint** (not individual instance endpoints) +- **Aurora Serverless v2** supports the same connection format +- **Multi-AZ deployments** will automatically failover if needed + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-aurora-cluster-endpoint:5432/your_database" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check security group rules + - Verify the cluster endpoint is correct + - Ensure the cluster is publicly accessible (if using public endpoint) + +2. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +3. **Cluster Parameter Group Issues**: + - Ensure your Aurora cluster supports the required features + - Check if any cluster parameter group restrictions apply + +4. **Aurora Serverless v1 Limitations**: + - Aurora Serverless v1 has limited support for certain operations + - Consider upgrading to Aurora Serverless v2 if needed + +### SSL Configuration + +If you encounter SSL issues, add SSL parameters to your connection string: + +``` +postgresql://xata_snapshot:your_password@your-aurora-cluster-endpoint:5432/your_database?sslmode=require +``` + +## Aurora-Specific Features + +### Aurora Serverless v2 + +If you're using Aurora Serverless v2: + +- **Automatic scaling** won't affect the migration +- **Connection limits** may apply during scaling events +- **Consider migration timing** to avoid scaling operations + +### Multi-AZ Deployments + +For Multi-AZ Aurora clusters: + +- **Use the cluster endpoint** for automatic failover +- **Migration will continue** even if a failover occurs +- **Monitor cluster health** during migration + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication \ No newline at end of file diff --git a/docs/migrations/aws-rds.mdx b/docs/migrations/aws-rds.mdx new file mode 100644 index 0000000..6faeffd --- /dev/null +++ b/docs/migrations/aws-rds.mdx @@ -0,0 +1,152 @@ +--- +title: "Migrate from AWS RDS to Xata" +description: "Learn how to migrate your AWS RDS PostgreSQL database to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- AWS RDS PostgreSQL instance +- Access to AWS Console and RDS parameter groups +- Xata account and project setup +- Network access to your RDS instance + +## Create Snapshot User + +Connect to your RDS instance and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +> **Note:** The user only needs `SELECT` on all tables and `USAGE` on the schema. The `REPLICATION` privilege is NOT required for snapshot migrations with `xata clone`. + +## Network Configuration + +### Option 1: Public Endpoint (Less Secure) + +If your RDS instance has a public endpoint: + +1. **Whitelist Your IP**: + - Go to your RDS instance → Security Groups + - Add a rule allowing PostgreSQL (port 5432) from your IP address + - Or temporarily allow from anywhere (0.0.0.0/0) for migration + +### Option 2: VPC Access (Recommended) + +For better security, run the migration from within AWS: + +1. **EC2 Instance in Same VPC**: + - Launch an EC2 instance in the same VPC as your RDS + - Install Xata CLI on the EC2 instance + - Run the migration from there + +2. **GitHub Actions with AWS Credentials**: + - Use GitHub Actions with AWS credentials + - Configure the action to run in your VPC + - Use the private endpoint for connection + +## Get Connection String + +### RDS Connection String Format + +``` +postgresql://xata_replication:your_password@your-rds-endpoint:5432/your_database +``` + +You can find the endpoint in the AWS RDS Console under your instance details. + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_replication:your_password@your-rds-endpoint:5432/your_database" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check security group rules + - Verify the endpoint is correct + - Ensure the instance is publicly accessible (if using public endpoint) + +2. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +3. **Parameter Group Issues**: + - Ensure your RDS instance supports the required features + - Check if any parameter group restrictions apply + +### SSL Configuration + +If you encounter SSL issues, add SSL parameters to your connection string: + +``` +postgresql://xata_replication:your_password@your-rds-endpoint:5432/your_database?sslmode=require +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication \ No newline at end of file diff --git a/docs/migrations/azure.mdx b/docs/migrations/azure.mdx new file mode 100644 index 0000000..a0c95a0 --- /dev/null +++ b/docs/migrations/azure.mdx @@ -0,0 +1,243 @@ +--- +title: "Migrate from Azure Database for PostgreSQL to Xata" +description: "Learn how to migrate your Azure Database for PostgreSQL to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- Azure Database for PostgreSQL instance +- Access to Azure Portal +- Xata account and project setup +- Network access to your Azure database + + + +## Create Snapshot User + +Connect to your Azure database and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Public Access (Less Secure) + +If your Azure database allows public access: + +1. **Configure Firewall Rules**: + - Go to "Networking" in the left menu + - Add your IP address to "Firewall rules" + - Or temporarily add `0.0.0.0/0` for migration (remove after) + +### Option 2: Private Access (Recommended) + +For better security, use private endpoints: + +1. **Enable Private Endpoint**: + - Go to "Networking" in the left menu + - Enable "Private endpoint connections" + - Configure VNet integration + +2. **Run Migration from Azure**: + - Use Azure VM, App Service, or Azure Functions + - Ensure the service is in the same VNet as your database + +### Option 3: Azure Database for PostgreSQL Flexible Server + +If you're using Flexible Server: + +1. **Configure Network Access**: + - Go to "Networking" in the left menu + - Choose "Private access (VNet Integration)" or "Public access" + - Configure firewall rules accordingly + +## Get Connection String + +### Find Your Connection Details + +1. Go to Azure Portal → Your PostgreSQL Server +2. Click "Connection strings" in the left menu +3. Copy the connection string + +### Connection String Format + +Azure connection strings typically look like this: + +``` +postgresql://xata_snapshot:your_password@your-server.postgres.database.azure.com:5432/postgres?sslmode=require +``` + +### Azure-Specific Formatting + +Note that Azure often uses this format for usernames: + +``` +postgresql://xata_snapshot@your-server:your_password@your-server.postgres.database.azure.com:5432/postgres?sslmode=require +``` + +### SSL Configuration + +Azure requires SSL. Always include SSL parameters: + +``` +postgresql://user:password@host:port/database?sslmode=require +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot@your-server:your_password@your-server.postgres.database.azure.com:5432/postgres?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check firewall rules in Azure Portal + - Verify the server name is correct + - Ensure the server is running + +2. **SSL Connection Required**: + - Add `?sslmode=require` to your connection string + - Verify SSL is enabled on the Azure server + +3. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +4. **Parameter Changes Not Applied**: + - Wait for the server restart to complete + - Verify parameters are set correctly in Azure Portal + +5. **Username Format Issues**: + - Azure may require `username@servername` format + - Check the exact format in Azure connection strings + +### Using Azure CLI + +You can also configure Azure Database using Azure CLI: + +```bash +# Install Azure CLI if not already installed +curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + +# Login to Azure +az login + +# Set server parameters +az postgres server configuration set \ + --resource-group your-resource-group \ + --server-name your-server-name \ + --name wal_level \ + --value logical + +az postgres server configuration set \ + --resource-group your-resource-group \ + --server-name your-server-name \ + --name max_replication_slots \ + --value 5 + +az postgres server configuration set \ + --resource-group your-resource-group \ + --server-name your-server-name \ + --name max_wal_senders \ + --value 10 + +# Restart the server +az postgres server restart \ + --resource-group your-resource-group \ + --name your-server-name +``` + +## Security Best Practices + +1. **Use Private Endpoints** when possible +2. **Remove public access** after migration +3. **Use strong passwords** for replication users +4. **Limit firewall rules** to specific IPs +5. **Enable Azure Monitor** to track access + +## Azure Database for PostgreSQL Flexible Server + +If you're using Flexible Server, the process is similar but with some differences: + +### Flexible Server Parameters + +1. **Go to "Server parameters"** +2. **Set the same parameters** as above +3. **No restart required** for Flexible Server + +### Flexible Server Networking + +1. **Choose "Private access (VNet Integration)"** for better security +2. **Or use "Public access"** with firewall rules +3. **Configure Azure Private DNS** if using private access + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/digitalocean.mdx b/docs/migrations/digitalocean.mdx new file mode 100644 index 0000000..6a478f7 --- /dev/null +++ b/docs/migrations/digitalocean.mdx @@ -0,0 +1,282 @@ +--- +title: "Migrate from DigitalOcean Managed Databases to Xata" +description: "Learn how to migrate your DigitalOcean Managed Database for PostgreSQL to Xata using xata clone. Step-by-step instructions for enabling logical replication and configuring the migration." +--- + +## Prerequisites + +- DigitalOcean Managed Database for PostgreSQL +- Access to DigitalOcean Console +- Xata account and project setup +- Network access to your DigitalOcean database + +## Enable Logical Replication + +### 1. Access DigitalOcean Console + +1. Go to [DigitalOcean Console](https://cloud.digitalocean.com) +2. Navigate to "Databases" in the left menu +3. Select your PostgreSQL database cluster + +### 2. Configure Database Settings + +1. **Go to "Settings"** tab +2. **Click "Edit Configuration"** +3. **Add the following configuration**: + ```json + { + "wal_level": "logical", + "max_replication_slots": 5, + "max_wal_senders": 10 + } + ``` +4. **Click "Save"** to apply changes +5. **Wait for the configuration** to be applied + +### 3. Verify Configuration + +Connect to your DigitalOcean database and verify the settings: + +```sql +-- Check if logical replication is enabled +SHOW wal_level; + +-- Check replication slots +SELECT * FROM pg_replication_slots; + +-- Check WAL senders +SHOW max_wal_senders; +``` + +## Create Snapshot User + +Connect to your DigitalOcean database and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Public Access + +If your database allows public access: + +1. **Go to "Settings"** → "Trusted Sources" +2. **Add your IP address** to the trusted sources +3. **Or temporarily add `0.0.0.0/0`** for migration (remove after) + +### Option 2: Private Network (Recommended) + +For better security, use private networking: + +1. **Enable Private Network**: + - Go to "Settings" → "Networking" + - Enable "Private Network" + - Note the private IP address + +2. **Run Migration from DigitalOcean**: + - Use a DigitalOcean Droplet in the same VPC + - Install Xata CLI on the Droplet + - Run the migration from there + +### Option 3: VPC Network + +If you have a VPC setup: + +1. **Configure VPC Peering** if needed +2. **Ensure your migration machine** is in the same VPC +3. **Use private IP addresses** for connection + +## Get Connection String + +### Find Your Connection Details + +1. Go to DigitalOcean Console → Your Database +2. Click "Connection Details" tab +3. Copy the connection string + +### Connection String Format + +DigitalOcean connection strings typically look like this: + +``` +postgresql://xata_snapshot:your_password@your-db-host:5432/defaultdb?sslmode=require +``` + +### Using the Snapshot User + +Replace the default user with your snapshot user: + +``` +postgresql://xata_snapshot:your_password@your-db-host:5432/defaultdb?sslmode=require +``` + +### SSL Configuration + +DigitalOcean requires SSL. Always include SSL parameters: + +``` +postgresql://user:password@host:port/database?sslmode=require +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-db-host:5432/defaultdb?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check trusted sources in DigitalOcean Console + - Verify the host address is correct + - Ensure the database is running + +2. **SSL Connection Required**: + - Add `?sslmode=require` to your connection string + - Verify SSL is enabled on the database + +3. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +4. **Configuration Not Applied**: + - Wait for configuration changes to apply + - Check database status in DigitalOcean Console + - Verify configuration in database settings + +### Using DigitalOcean CLI + +You can also configure your database using DigitalOcean CLI: + +```bash +# Install doctl if not already installed +snap install doctl + +# Authenticate +doctl auth init + +# List your databases +doctl databases list + +# Get connection details +doctl databases get your-database-id +``` + +## Security Best Practices + +1. **Use Private Networks** when possible +2. **Remove public access** after migration +3. **Use strong passwords** for snapshot users +4. **Limit trusted sources** to specific IPs +5. **Enable monitoring** to track access + +## Performance Considerations + +### Database Plans + +DigitalOcean offers different database plans: + +- **Basic**: 1GB RAM, 1 vCPU +- **Professional**: 2GB RAM, 1 vCPU +- **Professional-2**: 4GB RAM, 2 vCPU +- **Professional-4**: 8GB RAM, 4 vCPU + +### Migration Timing + +- **Choose appropriate plan** for your database size +- **Run during low-traffic** periods +- **Monitor resource usage** during migration +- **Consider upgrading** if needed for large datasets + +## Migration Strategies + +### Strategy 1: Direct Migration + +1. **Enable logical replication** in DigitalOcean +2. **Clone data** to Xata +3. **Update application** to use Xata +4. **Remove DigitalOcean database** + +### Strategy 2: Gradual Migration + +1. **Keep DigitalOcean database** during transition +2. **Clone data** to Xata +3. **Gradually migrate** features to Xata +4. **Eventually consolidate** to Xata + +### Strategy 3: Backup and Restore + +1. **Create DigitalOcean backup** +2. **Restore to Xata** +3. **Update application** +4. **Remove DigitalOcean database** + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/gcp-cloudsql.mdx b/docs/migrations/gcp-cloudsql.mdx new file mode 100644 index 0000000..a8abf7c --- /dev/null +++ b/docs/migrations/gcp-cloudsql.mdx @@ -0,0 +1,196 @@ +--- +title: "Migrate from GCP Cloud SQL to Xata" +description: "Learn how to migrate your GCP Cloud SQL PostgreSQL database to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- GCP Cloud SQL PostgreSQL instance +- Access to Google Cloud Console +- Xata account and project setup +- Network access to your Cloud SQL instance + + + +## Create Snapshot User + +Connect to your Cloud SQL instance and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Public IP (Less Secure) + +If your Cloud SQL instance has a public IP: + +1. **Authorize Your IP**: + - Go to your Cloud SQL instance → "Connections" → "Networking" + - Add your IP address to "Authorized networks" + - Or temporarily add `0.0.0.0/0` for migration (remove after) + +### Option 2: Private IP (Recommended) + +For better security, use private IP: + +1. **Enable Private IP**: + - Go to your Cloud SQL instance → "Connections" → "Networking" + - Enable "Private IP" + - Configure VPC peering if needed + +2. **Run Migration from GCP**: + - Use Cloud Run, Compute Engine, or Cloud Functions + - Ensure the service is in the same VPC as your Cloud SQL + +### Option 3: Cloud SQL Proxy + +Use Cloud SQL Proxy for secure connections: + +```bash +# Install Cloud SQL Proxy +curl -o cloud-sql-proxy https://dl.google.com/cloudsql/cloud-sql-proxy.linux.amd64 +chmod +x cloud-sql-proxy + +# Start the proxy +./cloud-sql-proxy --instances=your-project:your-region:your-instance +``` + +## Get Connection String + +### Connection String Format + +``` +postgresql://xata_snapshot:your_password@your-cloudsql-ip:5432/your_database +``` + +### Find Your Instance Details + +1. Go to Cloud SQL Console → Instances +2. Click on your instance +3. Note the **Public IP address** (if using public IP) +4. Note the **Private IP address** (if using private IP) + +### SSL Configuration + +Cloud SQL requires SSL by default. Include SSL parameters in your connection string: + +``` +postgresql://xata_snapshot:your_password@your-cloudsql-ip:5432/your_database?sslmode=require +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-cloudsql-ip:5432/your_database?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check authorized networks + - Verify the IP address is correct + - Ensure the instance is running + +2. **SSL Connection Required**: + - Add `?sslmode=require` to your connection string + - Or use Cloud SQL Proxy for secure connections + +3. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +4. **Database Flags Not Applied**: + - Wait for the instance restart to complete + - Verify flags are set correctly in the console + +### Using Cloud SQL Proxy + +If you're having connection issues, try using Cloud SQL Proxy: + +```bash +# Start proxy +./cloud-sql-proxy --instances=your-project:your-region:your-instance + +# Use localhost in connection string +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@localhost:5432/your_database" + +# Run migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Security Best Practices + +1. **Use Private IP** when possible +2. **Remove public IP access** after migration +3. **Use strong passwords** for snapshot users +4. **Limit authorized networks** to specific IPs +5. **Enable Cloud Audit Logs** to monitor access + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication \ No newline at end of file diff --git a/docs/migrations/heroku.mdx b/docs/migrations/heroku.mdx new file mode 100644 index 0000000..cfa6617 --- /dev/null +++ b/docs/migrations/heroku.mdx @@ -0,0 +1,276 @@ +--- +title: "Migrate from Heroku Postgres to Xata" +description: "Learn how to migrate your Heroku Postgres database to Xata using xata clone. Step-by-step instructions for enabling logical replication and configuring the migration." +--- + +## Prerequisites + +- Heroku Postgres add-on +- Heroku CLI installed +- Xata account and project setup +- Network access to your Heroku database + +## About Heroku Postgres + +Heroku Postgres offers different tiers with varying capabilities: + +- **Hobby/Basic**: Limited logical replication support +- **Standard/Premium**: Full logical replication support +- **Private**: Enhanced security and performance + +## Enable Logical Replication + +### 1. Check Your Plan + +First, verify your Heroku Postgres plan supports logical replication: + +```bash +# Check your database plan +heroku pg:info --app your-app-name +``` + +### 2. Upgrade if Necessary + +If you're on a Hobby plan, upgrade to Standard or higher: + +```bash +# Upgrade to Standard plan +heroku addons:upgrade heroku-postgresql:standard-0 --app your-app-name +``` + +### 3. Enable Logical Replication + +For Standard plans and above, logical replication is typically enabled by default. Verify: + +```bash +# Connect to your Heroku database +heroku pg:psql --app your-app-name + +# Check if logical replication is enabled +SHOW wal_level; +``` + +If logical replication is not enabled, you can enable it: + +```sql +-- Enable logical replication (requires Standard plan or higher) +ALTER SYSTEM SET wal_level = logical; +SELECT pg_reload_conf(); +``` + +### 4. Verify Configuration + +```sql +-- Check replication slots +SELECT * FROM pg_replication_slots; + +-- Check WAL senders +SHOW max_wal_senders; +``` + +## Create Snapshot User + +Connect to your Heroku database and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Get Connection String + +### Find Your Connection Details + +1. **Using Heroku CLI**: + ```bash + heroku config:get DATABASE_URL --app your-app-name + ``` + +2. **Using Heroku Dashboard**: + - Go to [Heroku Dashboard](https://dashboard.heroku.com) + - Select your app + - Go to "Resources" → "Heroku Postgres" + - Click "Settings" → "View Credentials" + +### Connection String Format + +Heroku connection strings typically look like this: + +``` +postgresql://xata_snapshot:your_password@your-heroku-host:5432/your_database?sslmode=require +``` + +### Using the Snapshot User + +Replace the default user with your snapshot user: + +``` +postgresql://xata_snapshot:your_password@your-heroku-host:5432/your_database?sslmode=require +``` + +## Network Configuration + +### Public Access + +Heroku Postgres databases are typically accessible from the internet: + +1. **No additional configuration** needed for public access +2. **SSL is required** by default +3. **Connection limits** may apply based on your plan + +### Private Access (Premium Plans) + +If you're on a Premium plan with private access: + +1. **Use Heroku Private Spaces** +2. **Configure VPC peering** if needed +3. **Run migration from within Heroku** environment + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-heroku-host:5432/your_database?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Logical Replication Not Supported**: + - Upgrade to Standard plan or higher + - Check plan limitations in Heroku documentation + +2. **Connection Limits**: + - Heroku has connection limits based on plan + - Consider running migration during low-traffic periods + - Monitor connection usage + +3. **SSL Issues**: + - Heroku requires SSL (`sslmode=require`) + - Verify SSL parameters in connection string + +4. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +### Using pg_dump as Alternative + +If `xata clone` cannot be used (e.g., Hobby plan), use `pg_dump`: + +```bash +# Create a dump +heroku pg:backups:capture --app your-app-name + +# Download the backup +heroku pg:backups:download --app your-app-name + +# Get Xata connection string +xata branch url + +# Restore to Xata +pg_restore --dbname "your-xata-connection-string" backup.dump +``` + +## Migration Strategies + +### Strategy 1: Direct Migration + +1. **Upgrade to Standard plan** if on Hobby +2. **Enable logical replication** +3. **Clone data** to Xata +4. **Update application** to use Xata +5. **Remove Heroku Postgres** add-on + +### Strategy 2: Backup and Restore + +1. **Create Heroku backup** +2. **Restore to Xata** +3. **Update application** +4. **Remove Heroku Postgres** + +### Strategy 3: Gradual Migration + +1. **Keep Heroku Postgres** during transition +2. **Clone data** to Xata +3. **Gradually migrate** features to Xata +4. **Eventually consolidate** to Xata + +## Performance Considerations + +### Plan Limitations + +- **Hobby**: 10,000 rows, 20 connections +- **Basic**: 10M rows, 120 connections +- **Standard**: 64M rows, 400 connections +- **Premium**: 2B rows, 500 connections + +### Migration Timing + +- **Run during low-traffic** periods +- **Monitor connection usage** +- **Consider database size** vs. plan limits + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/migrations.mdx b/docs/migrations/migrations.mdx new file mode 100644 index 0000000..3840976 --- /dev/null +++ b/docs/migrations/migrations.mdx @@ -0,0 +1,52 @@ +--- +title: Migration Guides +description: Migrate your existing PostgreSQL databases to Xata using xata clone +--- + +Migrate your existing PostgreSQL databases to Xata's platform using `xata clone`. This tool efficiently copies data between PostgreSQL instances, supporting both one-time migrations and continuous replication. + +## What is xata clone? + +[`xata clone`](/cli/clone) is a powerful tool for copying data between PostgreSQL instances efficiently. You can use it to import your existing database (from RDS, Cloud SQL, Supabase, etc.) into Xata with optional data anonymization for staging & development environments. + +## Prerequisites + +Before starting any migration: + +1. **Source Database Access**: You need the connection string (host, port, database name, user, password) of the source database +2. **User Privileges**: The user should have sufficient privileges to read data +4. **Network Access**: Ensure your environment can reach the source database +5. **Xata Project**: Create a Xata account and project with a target branch + +## Cloud Providers + +Migrate from major cloud PostgreSQL services: + +- **[AWS RDS & Aurora](/migrations/aws-rds)** - Amazon's managed PostgreSQL services +- **[GCP Cloud SQL](/migrations/gcp-cloudsql)** - Google Cloud's managed PostgreSQL +- **[Google AlloyDB](/migrations/alloydb)** - Google's PostgreSQL-compatible database +- **[Azure Database](/migrations/azure)** - Microsoft's managed PostgreSQL service + +## Self-Hosted & Other Providers + +Migrate from other PostgreSQL deployments: + +- **[Self-Hosted PostgreSQL](/migrations/self-hosted)** - Your own PostgreSQL instances +- **[Neon](/migrations/neon)** - Serverless PostgreSQL with branching +- **[Supabase](/migrations/supabase)** - Open source Firebase alternative +- **[Heroku Postgres](/migrations/heroku)** - Heroku's managed PostgreSQL +- **[DigitalOcean Managed Databases](/migrations/digitalocean)** - DigitalOcean's PostgreSQL service + +## Other Databases + +Migrate from non-PostgreSQL databases: + +- **[MySQL](/migrations/mysql)** - MySQL to PostgreSQL migration with schema conversion + +## Getting Help + +Need assistance with your migration? + +- Join our [Discord community](https://discord.gg/xata) for real-time help +- Explore [core concepts](/core-concepts) to understand Xata's features +- Check out [tutorials](/tutorials) for advanced workflows \ No newline at end of file diff --git a/docs/migrations/mysql.mdx b/docs/migrations/mysql.mdx new file mode 100644 index 0000000..bd1ba34 --- /dev/null +++ b/docs/migrations/mysql.mdx @@ -0,0 +1,335 @@ +--- +title: "Migrate from MySQL to Xata" +description: "Learn how to migrate your MySQL database to Xata using mysqldump and schema conversion. Step-by-step instructions for migrating from MySQL to PostgreSQL." +--- + +## Prerequisites + +- MySQL database (5.7 or 8.0) +- Access to MySQL server with sufficient privileges +- Xata account and project setup +- Network access to your MySQL server + +## Important Note + +Xata is built on PostgreSQL, so migrating from MySQL requires: +- **Schema conversion** from MySQL to PostgreSQL +- **Data export/import** using mysqldump +- **Manual verification** of data types and constraints + +## Create Migration User + +Connect to your MySQL database and create a dedicated user for migration: + +```sql +-- Create migration user +CREATE USER 'xata_migration'@'%' IDENTIFIED BY 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT, LOCK TABLES, SHOW VIEW ON *.* TO 'xata_migration'@'%'; + +-- For specific database +GRANT SELECT, LOCK TABLES, SHOW VIEW ON your_database.* TO 'xata_migration'@'%'; + +-- Flush privileges +FLUSH PRIVILEGES; +``` + +## Export MySQL Schema + +First, export your MySQL schema to understand the structure: + +```bash +# Export schema only +mysqldump --no-data --routines --triggers \ + -h your-mysql-host \ + -u xata_migration \ + -p your_database > schema.sql + +# Export with data +mysqldump --routines --triggers \ + -h your-mysql-host \ + -u xata_migration \ + -p your_database > full_backup.sql +``` + +## Schema Conversion + +### Common MySQL to PostgreSQL Conversions + +You'll need to convert your MySQL schema to PostgreSQL format: + +#### Data Types +```sql +-- MySQL -> PostgreSQL +INT -> INTEGER +BIGINT -> BIGINT +VARCHAR(255) -> VARCHAR(255) +TEXT -> TEXT +DATETIME -> TIMESTAMP +TIMESTAMP -> TIMESTAMP +BOOLEAN -> BOOLEAN +JSON -> JSONB +``` + +#### Auto Increment +```sql +-- MySQL +id INT AUTO_INCREMENT PRIMARY KEY + +-- PostgreSQL +id SERIAL PRIMARY KEY +-- or +id BIGSERIAL PRIMARY KEY +``` + +#### Indexes +```sql +-- MySQL +CREATE INDEX idx_name ON table_name(column_name); + +-- PostgreSQL (same syntax) +CREATE INDEX idx_name ON table_name(column_name); +``` + +#### Foreign Keys +```sql +-- MySQL +FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + +-- PostgreSQL (same syntax) +FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +``` + +## Export Data + +Export your MySQL data in a PostgreSQL-compatible format: + +```bash +# Export data in INSERT format +mysqldump --no-create-info --complete-insert \ + -h your-mysql-host \ + -u xata_migration \ + -p your_database > data.sql + +# Export specific tables +mysqldump --no-create-info --complete-insert \ + -h your-mysql-host \ + -u xata_migration \ + -p your_database table1 table2 > specific_tables.sql +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Create PostgreSQL Schema + +Create the converted schema in Xata: + +```bash +# Get your Xata connection string +xata branch url + +# Connect to Xata and create tables +psql "your-xata-connection-string" -f converted_schema.sql +``` + +## Import Data + +Import your converted data into Xata: + +```bash +# Import data +psql "your-xata-connection-string" -f converted_data.sql + +# Or import specific tables +psql "your-xata-connection-string" -f specific_tables_data.sql +``` + +## Alternative: Using pgloader + +For more automated conversion, you can use `pgloader`: + +```bash +# Install pgloader +# Ubuntu/Debian +sudo apt-get install pgloader + +# macOS +brew install pgloader + +# Create a conversion file (convert.load) +LOAD DATABASE + FROM mysql://xata_migration:password@your-mysql-host/your_database + INTO postgresql://your-xata-connection-string + +WITH include drop, create tables, create indexes, reset sequences, + workers = 8, concurrency = 1, + multiple readers per thread, rows per range = 50000 + +SET MySQL PARAMETERS + net_read_timeout = '600', + net_write_timeout = '600' + +SET PostgreSQL PARAMETERS + maintenance_work_mem to '128 MB', + work_mem to '12 MB', + search_path to 'public' + +CAST + type int with extra auto_increment to serial, + type int with extra auto_increment to bigserial when (> precision 10), + -- Add more type conversions as needed + default value null for drop typemod keep default keep not null; + +# Run the conversion +pgloader convert.load +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + + -- Check foreign key relationships + SELECT COUNT(*) FROM table1 t1 + JOIN table2 t2 ON t1.id = t2.table1_id; + ``` + +3. **Test Application**: Verify your application works with the new PostgreSQL schema + +## Common Conversion Issues + +### 1. Date/Time Handling + +MySQL and PostgreSQL handle dates differently: + +```sql +-- MySQL +SELECT NOW(); + +-- PostgreSQL +SELECT NOW(); +-- or +SELECT CURRENT_TIMESTAMP; +``` + +### 2. String Functions + +Some string functions differ: + +```sql +-- MySQL +CONCAT(str1, str2) +SUBSTRING(str, pos, len) + +-- PostgreSQL +str1 || str2 +SUBSTRING(str FROM pos FOR len) +``` + +### 3. JSON Handling + +```sql +-- MySQL +JSON_EXTRACT(column, '$.key') + +-- PostgreSQL +column->>'key' +``` + +### 4. Auto Increment Sequences + +After importing, reset sequences: + +```sql +-- For each table with auto increment +SELECT setval('table_name_id_seq', (SELECT MAX(id) FROM table_name)); +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Issues**: + - Verify MySQL server is accessible + - Check user privileges + - Ensure network connectivity + +2. **Schema Conversion Errors**: + - Review MySQL-specific features + - Convert data types manually + - Handle MySQL-specific functions + +3. **Data Import Errors**: + - Check for encoding issues + - Verify data type compatibility + - Handle NULL values appropriately + +4. **Performance Issues**: + - Import data in batches + - Disable indexes during import + - Rebuild indexes after import + +### Using mysqldump with Options + +```bash +# Export with specific options +mysqldump \ + --single-transaction \ + --routines \ + --triggers \ + --set-gtid-purged=OFF \ + --default-character-set=utf8mb4 \ + -h your-mysql-host \ + -u xata_migration \ + -p your_database > backup.sql +``` + +## Migration Strategies + +### Strategy 1: Full Migration + +1. **Export MySQL schema and data** +2. **Convert schema to PostgreSQL** +3. **Import into Xata** +4. **Update application** to use Xata +5. **Decommission MySQL** + +### Strategy 2: Gradual Migration + +1. **Migrate read-only data** first +2. **Keep MySQL for writes** during transition +3. **Gradually migrate** write operations +4. **Eventually consolidate** to Xata + +### Strategy 3: Parallel Systems + +1. **Keep MySQL** for existing features +2. **Use Xata** for new features +3. **Sync data** between systems +4. **Gradually migrate** features + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/neon.mdx b/docs/migrations/neon.mdx new file mode 100644 index 0000000..0d2d607 --- /dev/null +++ b/docs/migrations/neon.mdx @@ -0,0 +1,216 @@ +--- +title: "Migrate from Neon to Xata" +description: "Learn how to migrate your Neon PostgreSQL database to Xata using xata clone. Step-by-step instructions for leveraging Neon's branching features." +--- + +## Prerequisites + +- Neon PostgreSQL project +- Access to Neon Console +- Xata account and project setup +- Network access to your Neon database + +## About Neon and Xata + +Both Neon and Xata offer PostgreSQL branching capabilities, but they serve different purposes: + +- **Neon**: Serverless PostgreSQL with branching for development +- **Xata**: PostgreSQL with branching for production workflows and data operations + +Migrating from Neon to Xata allows you to: +- Use Neon branches as staging data for Xata production +- Leverage Xata's advanced data operations and branching workflows +- Take advantage of Xata's deployment flexibility (SaaS or BYOC) + + + +## Create Snapshot User + +Connect to your Neon database and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Get Connection String + +### Find Your Connection Details + +1. Go to Neon Console → Your Project +2. Click on your database branch +3. Copy the connection string from the "Connection Details" section + +### Connection String Format + +Neon connection strings typically look like this: + +``` +postgresql://xata_snapshot:your_password@ep-cool-name-123456.us-east-2.aws.neon.tech/neondb?sslmode=require +``` + +### Branch-Specific Connection + +If you want to clone from a specific Neon branch: + +1. **Create or select a branch** in Neon Console +2. **Use that branch's connection string** +3. **The clone will copy that branch's data** + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@ep-cool-name-123456.us-east-2.aws.neon.tech/neondb?sslmode=require" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Neon Branching Strategy + +### Option 1: Clone Production Branch + +Clone your Neon production branch to Xata: + +```bash +# Use production branch connection string +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:password@ep-prod-branch.us-east-2.aws.neon.tech/neondb?sslmode=require" +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +### Option 2: Clone Development Branch + +Clone a Neon development branch for testing: + +```bash +# Use development branch connection string +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:password@ep-dev-branch.us-east-2.aws.neon.tech/neondb?sslmode=require" +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +### Option 3: Continuous Sync from Production + +Keep Xata in sync with Neon production: + +```bash +# Set up continuous sync +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL --continuous +``` + +## Troubleshooting + +### Common Issues + +1. **Connection Issues**: + - Check that SSL is enabled (`sslmode=require`) + - Verify the connection string format + - Ensure the user has correct privileges + +2. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +3. **Branch Not Found**: + - Ensure you're using the correct branch connection string + - Verify the branch exists in Neon Console + + + +### SSL Configuration + +Neon requires SSL. Always include SSL parameters: + +``` +postgresql://user:password@host/database?sslmode=require +``` + +## Migration Strategies + +### Strategy 1: One-Time Migration + +For complete migration from Neon to Xata: + +1. **Clone production data** to Xata +2. **Update application** to use Xata connection +3. **Verify functionality** with Xata +4. **Decommission Neon** when ready + +### Strategy 2: Hybrid Approach + +Keep both systems during transition: + +1. **Clone Neon to Xata** for development +2. **Use Xata for new features** +3. **Gradually migrate** existing features +4. **Maintain sync** during transition + +### Strategy 3: Staging Workflow + +Use Neon for development, Xata for production: + +1. **Develop on Neon** branches +2. **Clone to Xata** for staging +3. **Deploy from Xata** to production +4. **Use Xata branching** for production workflows + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/self-hosted.mdx b/docs/migrations/self-hosted.mdx new file mode 100644 index 0000000..83b0c7e --- /dev/null +++ b/docs/migrations/self-hosted.mdx @@ -0,0 +1,244 @@ +--- +title: "Migrate from Self-Hosted PostgreSQL to Xata" +description: "Learn how to migrate your self-hosted PostgreSQL database to Xata using xata clone. Step-by-step instructions for configuring the migration." +--- + +## Prerequisites + +- Self-hosted PostgreSQL instance +- Access to PostgreSQL configuration files +- Xata account and project setup +- Network access to your PostgreSQL server + + + +## Create Snapshot User + +Connect to your PostgreSQL instance and create a dedicated user for migration: + +```sql +-- Create snapshot user (no REPLICATION privilege needed) +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Network Configuration + +### Option 1: Direct Connection + +If your PostgreSQL server is accessible from the internet: + +1. **Ensure PostgreSQL is listening** on the correct interface: + ```bash + # In postgresql.conf + listen_addresses = '*' + ``` + +2. **Configure firewall** to allow PostgreSQL port (5432): + ```bash + # Linux (ufw) + sudo ufw allow 5432/tcp + + # Linux (iptables) + sudo iptables -A INPUT -p tcp --dport 5432 -j ACCEPT + + # macOS + sudo pfctl -e + # Add rule to /etc/pf.conf + ``` + +### Option 2: SSH Tunnel (Recommended) + +For better security, use SSH tunneling: + +```bash +# Create SSH tunnel +ssh -L 5432:localhost:5432 user@your-postgres-server + +# Then connect to localhost:5432 from your migration machine +``` + +### Option 3: VPN Connection + +If you have a VPN setup: + +1. **Connect to your VPN** +2. **Use the internal IP** of your PostgreSQL server +3. **Ensure VPN allows** PostgreSQL traffic + +## Get Connection String + +### Connection String Format + +``` +postgresql://xata_snapshot:your_password@your-server-ip:5432/your_database +``` + +### SSL Configuration + +If you have SSL certificates configured: + +``` +postgresql://xata_snapshot:your_password@your-server-ip:5432/your_database?sslmode=require +``` + +If you don't have SSL configured: + +``` +postgresql://xata_snapshot:your_password@your-server-ip:5432/your_database?sslmode=disable +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with optional anonymization: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@your-server-ip:5432/your_database" + +# Configure anonymization rules +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Troubleshooting + +### Common Issues + +1. **Connection Refused**: + - Check if PostgreSQL is running + - Verify the port is correct (default 5432) + - Check firewall settings + - Ensure PostgreSQL is listening on the correct interface + +2. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + - Ensure pg_hba.conf allows connections + +3. **Configuration Not Applied**: + - Restart PostgreSQL after configuration changes + - Check PostgreSQL logs for errors + - Verify configuration file syntax + +4. **Network Issues**: + - Test connectivity with `telnet your-server-ip 5432` + - Check if the server is reachable from your migration machine + - Verify DNS resolution if using hostnames + +### PostgreSQL Logs + +Check PostgreSQL logs for errors: + +```bash +# Common log locations: +# Linux: /var/log/postgresql/postgresql-{version}-main.log +# macOS: /usr/local/var/log/postgresql.log +# Windows: PostgreSQL installation directory + +# Check logs in real-time +tail -f /var/log/postgresql/postgresql-{version}-main.log +``` + +### Using pg_dump as Alternative + +If `xata clone` cannot be used, you can use `pg_dump`: + +```bash +# Create a dump +pg_dump --format=custom -h your-server-ip -U xata_snapshot -d your_database -f backup.dump + +# Get Xata connection string +xata branch url + +# Restore to Xata +pg_restore --dbname "your-xata-connection-string" backup.dump +``` + +## Security Best Practices + +1. **Use SSH tunneling** when possible +2. **Configure firewall rules** to limit access +3. **Use strong passwords** for replication users +4. **Enable SSL** if PostgreSQL is exposed to the internet +5. **Monitor access logs** regularly + +## Performance Considerations + +### Large Databases + +For large databases, consider: + +1. **Increase WAL settings**: + ```bash + # In postgresql.conf + max_wal_senders = 20 + max_replication_slots = 10 + ``` + +2. **Optimize network**: + - Use wired connections when possible + - Ensure sufficient bandwidth + - Consider running migration during low-traffic periods + +3. **Monitor resources**: + - Check CPU and memory usage + - Monitor disk I/O + - Watch network utilization + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file diff --git a/docs/migrations/supabase.mdx b/docs/migrations/supabase.mdx new file mode 100644 index 0000000..6ad5739 --- /dev/null +++ b/docs/migrations/supabase.mdx @@ -0,0 +1,247 @@ +--- +title: "Migrate from Supabase to Xata" +description: "Learn how to migrate your Supabase PostgreSQL database to Xata using xata clone. Step-by-step instructions for handling Supabase-specific schemas and features." +--- + +## Prerequisites + +- Supabase project +- Access to Supabase Dashboard +- Xata account and project setup +- Network access to your Supabase database + +## About Supabase and Xata + +Supabase and Xata both provide PostgreSQL, but with different approaches: + +- **Supabase**: PostgreSQL with built-in Auth, Storage, and Realtime features +- **Xata**: Vanilla PostgreSQL with advanced branching and data operations + +### What Gets Migrated + +When you clone a Supabase database to Xata: + +✅ **What's Included**: +- All your custom tables and data +- Database schema and relationships +- Stored procedures and functions +- Custom extensions you've installed + +⚠️ **What's Not Included**: +- Supabase Auth (users, sessions, policies) +- Supabase Storage (file storage) +- Supabase Realtime (websocket subscriptions) +- Supabase Edge Functions +- Supabase-specific extensions (auth, storage, etc.) + + +## Create Snapshot User + +Connect to your Supabase database and create a dedicated user for migration: + +```sql +-- Create snapshot user +CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; + +-- Grant necessary privileges +GRANT SELECT ON ALL TABLES IN SCHEMA public TO xata_snapshot; +GRANT USAGE ON SCHEMA public TO xata_snapshot; + +-- Grant privileges on future tables +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; +``` + +## Get Connection String + +### Find Your Connection Details + +1. Go to Supabase Dashboard → Your Project +2. Navigate to "Settings" → "Database" +3. Copy the connection string from "Connection string" section + +### Connection String Format + +Supabase connection strings typically look like this: + +``` +postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres +``` + +### Using the Snapshot User + +Replace the default `postgres` user with your snapshot user: + +``` +postgresql://xata_snapshot:your_password@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres +``` + +## Handle Supabase Schemas + +### Option 1: Clone Everything (Including Supabase Schemas) + +This will clone all data including Supabase's internal schemas: + +```bash +# Clone everything +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:password@db.project-ref.supabase.co:5432/postgres" +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +### Option 2: Exclude Supabase Schemas (Recommended) + +Clone only your application data, excluding Supabase-specific schemas: + +```bash +# Configure clone to exclude Supabase schemas +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt + +# During configuration, exclude these schemas: +# - auth (Supabase authentication) +# - storage (Supabase file storage) +# - graphql_public (Supabase GraphQL) +# - realtime (Supabase realtime) +# - vault (Supabase secrets) +``` + +## Initialize Xata Project + +Set up your Xata project configuration: + +```bash +xata init +``` + +## Configure the Migration + +Set up your clone configuration with schema filtering: + +```bash +# Set your source URL +export XATA_CLI_SOURCE_POSTGRES_URL="postgresql://xata_snapshot:your_password@db.project-ref.supabase.co:5432/postgres" + +# Configure with schema filtering +xata clone config --source-url $XATA_CLI_SOURCE_POSTGRES_URL --mode prompt +``` + +## Start the Migration + +Begin the data transfer: + +```bash +# Start the migration +xata clone start --source-url $XATA_CLI_SOURCE_POSTGRES_URL +``` + +## Monitor Progress + +Check the migration status: + +```bash +xata clone status +``` + +## Verification + +After migration, verify your data: + +1. **Connect to Xata Branch**: + ```bash + xata branch url + ``` + +2. **Check Data Integrity**: + ```sql + -- Compare row counts + SELECT COUNT(*) FROM your_table; + + -- Check sample data + SELECT * FROM your_table LIMIT 10; + ``` + +3. **Test Relationships**: Verify foreign key relationships work correctly + +## Post-Migration Setup + +### 1. Set Up Authentication + +Since Supabase Auth won't be available, you'll need to implement authentication: + +- **Option A**: Use a third-party auth provider (Auth0, Clerk, etc.) +- **Option B**: Implement custom authentication +- **Option C**: Use Xata's built-in authentication features (if available) + +### 2. Handle File Storage + +Replace Supabase Storage with an alternative: + +- **Option A**: Use cloud storage (AWS S3, Google Cloud Storage) +- **Option B**: Use a CDN service +- **Option C**: Implement your own file storage solution + +### 3. Replace Realtime Features + +If you were using Supabase Realtime: + +- **Option A**: Use WebSockets with your backend +- **Option B**: Use a realtime service (Pusher, Ably) +- **Option C**: Implement polling for simple use cases + +## Migration Strategies + +### Strategy 1: Gradual Migration + +1. **Clone data** to Xata +2. **Migrate authentication** first +3. **Update file storage** implementation +4. **Replace realtime features** +5. **Switch application** to Xata + +### Strategy 2: Parallel Systems + +1. **Keep Supabase** for Auth/Storage/Realtime +2. **Use Xata** for database operations +3. **Gradually migrate** features to Xata +4. **Eventually consolidate** to Xata + +### Strategy 3: Clean Slate + +1. **Clone only application data** (exclude Supabase schemas) +2. **Implement new auth/storage/realtime** solutions +3. **Build new features** on Xata +4. **Migrate existing features** gradually + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: + - Verify the snapshot user has correct privileges + - Check that the user has sufficient permissions + +2. **Schema Conflicts**: + - Exclude Supabase schemas during configuration + - Use schema filtering in clone config + +3. **Connection Issues**: + - Verify the connection string format + - Check that SSL is enabled + - Ensure the database is accessible + +4. **Large Dataset**: + - Consider breaking the migration into smaller chunks + - Use continuous sync for ongoing updates + +### SSL Configuration + +Supabase requires SSL. Include SSL parameters: + +``` +postgresql://user:password@host/database?sslmode=require +``` + +## Next Steps + +- Explore [Xata branching](/core-concepts/branching) for development workflows +- Learn about [schema changes](/core-concepts/schema-changes) with zero downtime +- Set up [continuous sync](/tutorials/create-staging-replica) for ongoing replication +- Consider [deployment options](/deployment) for your Xata instance \ No newline at end of file From 70696fe6507ea4d55dd5b958ab41c5221bd3e87e Mon Sep 17 00:00:00 2001 From: Comhghall McKeating Date: Tue, 15 Jul 2025 16:42:30 +0100 Subject: [PATCH 10/13] Fixed obvious errors in quickstart guides. --- docs/quickstarts/astro.mdx | 64 ++++++++++++++++++++++++++++++++++-- docs/quickstarts/gorm.mdx | 1 + docs/quickstarts/java.mdx | 4 +-- docs/quickstarts/jpa.mdx | 35 +++----------------- docs/quickstarts/nuxt.mdx | 6 ++++ docs/quickstarts/remix.mdx | 1 + docs/quickstarts/typeorm.mdx | 27 +++++++-------- 7 files changed, 91 insertions(+), 47 deletions(-) diff --git a/docs/quickstarts/astro.mdx b/docs/quickstarts/astro.mdx index 799f8b9..d271214 100644 --- a/docs/quickstarts/astro.mdx +++ b/docs/quickstarts/astro.mdx @@ -15,7 +15,7 @@ First, set up your Xata database with the common e-commerce dataset: 1. Create a project and branch in the [Xata console](https://console.xata.io) 2. Navigate to the **Queries** tab in your branch -3. Run the following SQL commands to create the initial schema: +3. Run the following SQL commands to create the initial schema and populate the tables: ```sql CREATE TABLE products ( @@ -36,6 +36,9 @@ CREATE TABLE order_items ( qty INTEGER NOT NULL, PRIMARY KEY (order_id, product_id) ); + +INSERT INTO products(name,price) SELECT LEFT(md5(i::text),8),(random()*90+10)::numeric(7,2) FROM generate_series(1,10)i; +WITH o AS (INSERT INTO orders DEFAULT VALUES RETURNING id) INSERT INTO order_items(order_id,product_id,qty) SELECT o.id,pid,(1+floor(random()*3))::int FROM o,(SELECT id pid FROM products ORDER BY random() LIMIT 5)p; ``` ## Create Astro Project @@ -110,7 +113,7 @@ Create an API endpoint to fetch products: import type { APIRoute } from 'astro'; import { sql } from '../../lib/db'; -export const get: APIRoute = async () => { +export const GET: APIRoute = async () => { try { const products = await sql`SELECT * FROM products`; return new Response(JSON.stringify(products), { @@ -174,7 +177,64 @@ useEffect(() => { ``` +## Create Products List Component + +Create a products list component: + +```astro +import React, { useEffect, useState } from 'react'; + +export default function ProductList() { + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch('/api/products') + .then(res => res.json()) + .then(data => { + setProducts(data); + setLoading(false); + }) + .catch(() => setLoading(false)); + }, []); + + if (loading) return
Loading products...
; + + return ( +
+ {products.map(product => ( +
+

{product.name}

+

${product.price}

+ {product.rating && ( +
+ {product.rating}/5 +
+ )} +
+ ))} +
+ ); +} +``` + +## Modify index.astro + +``` +--- +import ProductList from '../components/ProductsList.jsx'; +--- + + + Xata E-commerce Store + + +

Xata E-commerce Store

+ + + +``` ## Run the Application diff --git a/docs/quickstarts/gorm.mdx b/docs/quickstarts/gorm.mdx index bfb01c3..6ae5c1f 100644 --- a/docs/quickstarts/gorm.mdx +++ b/docs/quickstarts/gorm.mdx @@ -667,6 +667,7 @@ To run the HTTP API: ```bash go get github.com/gin-gonic/gin +go get my-xata-app/config go run api/main.go ``` diff --git a/docs/quickstarts/java.mdx b/docs/quickstarts/java.mdx index 1c787f7..d0c0776 100644 --- a/docs/quickstarts/java.mdx +++ b/docs/quickstarts/java.mdx @@ -44,10 +44,10 @@ CREATE TABLE order_items ( Create a new Spring Boot project using Spring Initializr: ```bash -curl https://start.spring.io/starter.tgz \ +curl -X POST https://start.spring.io/starter.tgz \ -d type=maven-project \ -d language=java \ - -d bootVersion=3.2.0 \ + -d bootVersion=3.3.0 \ -d baseDir=my-xata-app \ -d groupId=com.example \ -d artifactId=my-xata-app \ diff --git a/docs/quickstarts/jpa.mdx b/docs/quickstarts/jpa.mdx index fdc4ce2..aa283d7 100644 --- a/docs/quickstarts/jpa.mdx +++ b/docs/quickstarts/jpa.mdx @@ -158,7 +158,6 @@ xata branch url Create the JPA configuration file: ```xml - allProducts = productService.getAllProducts(); for (Product product : allProducts) { String rating = product.getRating() != null ? product.getRating().toString() : "N/A"; - System.out.printf("- %s: $%s (Rating: %s/5)%n", + System.out.printf("- %s: $%s (Rating: %s/5)%n", product.getName(), product.getPrice(), rating); } @@ -655,35 +652,13 @@ public class Main { System.out.printf("- %s: $%s%n", product.getName(), product.getPrice()); } - // Create an order with items - System.out.println("\nCreating an order..."); - var order = orderService.createOrder(); - System.out.println("Created order #" + order.getId()); - - // Add items to order - if (!products.isEmpty()) { - orderService.addItemToOrder(order.getId(), products.get(0).getId(), 2); // 2 headphones - if (products.size() >= 3) { - orderService.addItemToOrder(order.getId(), products.get(2).getId(), 1); // 1 laptop - } - } - - // Get order details with products - System.out.println("\nOrder details:"); - List> orderDetails = orderService.getOrdersWithProductDetails(); - for (Map detail : orderDetails) { - System.out.printf("Order #%s: %s x%s = $%s%n", - detail.get("order_id"), detail.get("product_name"), - detail.get("qty"), detail.get("total")); - } - // Update a product if (!products.isEmpty()) { System.out.println("\nUpdating product..."); var updatedProduct = productService.updateProduct( - products.get(0).getId(), - "Premium Wireless Headphones", - new BigDecimal("129.99"), + products.get(0).getId(), + "Premium Wireless Headphones", + new BigDecimal("129.99"), null ); if (updatedProduct.isPresent()) { @@ -698,7 +673,7 @@ public class Main { } finally { DatabaseUtil.closeEntityManagerFactory(); } - } + } } ``` diff --git a/docs/quickstarts/nuxt.mdx b/docs/quickstarts/nuxt.mdx index 7b7d257..3913fdd 100644 --- a/docs/quickstarts/nuxt.mdx +++ b/docs/quickstarts/nuxt.mdx @@ -241,7 +241,13 @@ export default defineNuxtConfig({ }); ``` +## Create app.vue +``` + +``` ## Run the Application diff --git a/docs/quickstarts/remix.mdx b/docs/quickstarts/remix.mdx index 7810887..f0c3b64 100644 --- a/docs/quickstarts/remix.mdx +++ b/docs/quickstarts/remix.mdx @@ -108,6 +108,7 @@ Create a loader to fetch products: ```typescript // app/routes/_index.tsx import type { LoaderFunction } from '@remix-run/node'; +import { useLoaderData } from '@remix-run/react'; import { json } from '@remix-run/node'; import { sql } from '~/db'; diff --git a/docs/quickstarts/typeorm.mdx b/docs/quickstarts/typeorm.mdx index c25cdab..176fbac 100644 --- a/docs/quickstarts/typeorm.mdx +++ b/docs/quickstarts/typeorm.mdx @@ -156,19 +156,19 @@ import { OrderItem } from './OrderItem' @Entity('products') export class Product { @PrimaryGeneratedColumn() - id: number + id!: number @Column({ type: 'text', nullable: false }) - name: string + name!: string @Column({ type: 'decimal', precision: 7, scale: 2, nullable: false }) - price: number + price!: number @Column({ type: 'integer', nullable: true }) - rating: number + rating!: number @OneToMany(() => OrderItem, orderItem => orderItem.product) - orderItems: OrderItem[] + orderItems!: OrderItem[] } ``` @@ -180,13 +180,13 @@ import { OrderItem } from './OrderItem' @Entity('orders') export class Order { @PrimaryGeneratedColumn() - id: number + id!: number @CreateDateColumn({ type: 'timestamp with time zone' }) - created_at: Date + created_at!: Date @OneToMany(() => OrderItem, orderItem => orderItem.order) - orderItems: OrderItem[] + orderItems!: OrderItem[] } ``` @@ -199,21 +199,21 @@ import { Order } from './Order' @Entity('order_items') export class OrderItem { @PrimaryColumn({ type: 'integer' }) - order_id: number + order_id!: number @PrimaryColumn({ type: 'integer' }) - product_id: number + product_id!: number @Column({ type: 'integer', nullable: false }) - qty: number + qty!: number @ManyToOne(() => Order, order => order.orderItems) @JoinColumn({ name: 'order_id' }) - order: Order + order!: Order @ManyToOne(() => Product, product => product.orderItems) @JoinColumn({ name: 'product_id' }) - product: Product + product!: Product } ``` @@ -355,6 +355,7 @@ import 'reflect-metadata' import { initializeDatabase } from './config/database' import { ProductService } from './services/ProductService' import { OrderService } from './services/OrderService' +import { AppDataSource } from './data-source'; // Adjust the path if needed async function main() { try { From 85fcc09d4dc828284039fe6c885529a1cc814a0f Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Thu, 17 Jul 2025 15:47:46 -0400 Subject: [PATCH 11/13] removing a comment --- docs/migrations/aws-rds.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/migrations/aws-rds.mdx b/docs/migrations/aws-rds.mdx index 6faeffd..29e6c83 100644 --- a/docs/migrations/aws-rds.mdx +++ b/docs/migrations/aws-rds.mdx @@ -15,7 +15,7 @@ description: "Learn how to migrate your AWS RDS PostgreSQL database to Xata usin Connect to your RDS instance and create a dedicated user for migration: ```sql --- Create snapshot user (no REPLICATION privilege needed) +-- Create snapshot user CREATE USER xata_snapshot WITH LOGIN PASSWORD 'your_secure_password'; -- Grant necessary privileges @@ -26,8 +26,6 @@ GRANT USAGE ON SCHEMA public TO xata_snapshot; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO xata_snapshot; ``` -> **Note:** The user only needs `SELECT` on all tables and `USAGE` on the schema. The `REPLICATION` privilege is NOT required for snapshot migrations with `xata clone`. - ## Network Configuration ### Option 1: Public Endpoint (Less Secure) From cf81ad12dde377d8052644c224b73a5c4376f4b6 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Thu, 17 Jul 2025 15:50:23 -0400 Subject: [PATCH 12/13] nav changes --- docs/config.json | 49 ++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/docs/config.json b/docs/config.json index 54ac150..4321006 100644 --- a/docs/config.json +++ b/docs/config.json @@ -145,10 +145,25 @@ "file": "docs/migrations/migrations.mdx", "items": [ { - "title": "AWS RDS & Aurora", + "title": "AWS Aurora", + "href": "/migrations/aws-aurora", + "file": "docs/migrations/aws-aurora.mdx" + }, + { + "title": "AWS RDS", "href": "/migrations/aws-rds", "file": "docs/migrations/aws-rds.mdx" }, + { + "title": "Azure Database", + "href": "/migrations/azure", + "file": "docs/migrations/azure.mdx" + }, + { + "title": "DigitalOcean Managed Databases", + "href": "/migrations/digitalocean", + "file": "docs/migrations/digitalocean.mdx" + }, { "title": "GCP Cloud SQL", "href": "/migrations/gcp-cloudsql", @@ -160,39 +175,29 @@ "file": "docs/migrations/alloydb.mdx" }, { - "title": "Azure Database", - "href": "/migrations/azure", - "file": "docs/migrations/azure.mdx" + "title": "Heroku Postgres", + "href": "/migrations/heroku", + "file": "docs/migrations/heroku.mdx" + }, + { + "title": "MySQL", + "href": "/migrations/mysql", + "file": "docs/migrations/mysql.mdx" }, { "title": "Neon", "href": "/migrations/neon", "file": "docs/migrations/neon.mdx" }, - { - "title": "Supabase", - "href": "/migrations/supabase", - "file": "docs/migrations/supabase.mdx" - }, { "title": "Self-Hosted PostgreSQL", "href": "/migrations/self-hosted", "file": "docs/migrations/self-hosted.mdx" }, { - "title": "Heroku Postgres", - "href": "/migrations/heroku", - "file": "docs/migrations/heroku.mdx" - }, - { - "title": "DigitalOcean Managed Databases", - "href": "/migrations/digitalocean", - "file": "docs/migrations/digitalocean.mdx" - }, - { - "title": "MySQL", - "href": "/migrations/mysql", - "file": "docs/migrations/mysql.mdx" + "title": "Supabase", + "href": "/migrations/supabase", + "file": "docs/migrations/supabase.mdx" } ] }, From 745cc8adddb2d89f0545638a6c7c8bf0816d30b6 Mon Sep 17 00:00:00 2001 From: alexfrancoeur Date: Thu, 17 Jul 2025 16:17:22 -0400 Subject: [PATCH 13/13] updated nav ordering --- docs/config.json | 98 ++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/config.json b/docs/config.json index 4321006..9ceacac 100644 --- a/docs/config.json +++ b/docs/config.json @@ -10,6 +10,55 @@ "href": "/getting-started", "file": "docs/getting-started.mdx" }, + { + "title": "Deployment models", + "href": "/deployment", + "file": "docs/deployment.mdx" + }, + { + "title": "Core concepts", + "href": "/core-concepts", + "file": "docs/core-concepts/core-concepts.mdx", + "items": [ + { + "title": "Instant branching", + "href": "/core-concepts/branching", + "file": "docs/core-concepts/branching.mdx" + }, + { + "title": "Data anonymization", + "href": "/core-concepts/anonymization", + "file": "docs/core-concepts/anonymization.mdx" + }, + { + "title": "Schema changes", + "href": "/core-concepts/schema-changes", + "file": "docs/core-concepts/schema-changes.mdx" + } + ] + }, + { + "title": "Tutorials", + "href": "/tutorials", + "file": "docs/tutorials/tutorials.mdx", + "items": [ + { + "title": "Set up staging replica", + "href": "/tutorials/create-staging-replica", + "file": "docs/tutorials/create-staging-replica.mdx" + }, + { + "title": "Migrate to Xata", + "href": "/tutorials/migrate-to-xata", + "file": "docs/tutorials/migrate-to-xata.mdx" + }, + { + "title": "Schema changes", + "href": "/tutorials/schema-change", + "file": "docs/tutorials/schema-change.mdx" + } + ] + }, { "title": "Quickstarts", "href": "/quickstarts", @@ -112,33 +161,6 @@ } ] }, - { - "title": "Deployment models", - "href": "/deployment", - "file": "docs/deployment.mdx" - }, - { - "title": "Tutorials", - "href": "/tutorials", - "file": "docs/tutorials/tutorials.mdx", - "items": [ - { - "title": "Set up staging replica", - "href": "/tutorials/create-staging-replica", - "file": "docs/tutorials/create-staging-replica.mdx" - }, - { - "title": "Migrate to Xata", - "href": "/tutorials/migrate-to-xata", - "file": "docs/tutorials/migrate-to-xata.mdx" - }, - { - "title": "Schema changes", - "href": "/tutorials/schema-change", - "file": "docs/tutorials/schema-change.mdx" - } - ] - }, { "title": "Migration Guides", "href": "/migrations", @@ -201,28 +223,6 @@ } ] }, - { - "title": "Core concepts", - "href": "/core-concepts", - "file": "docs/core-concepts/core-concepts.mdx", - "items": [ - { - "title": "Instant branching", - "href": "/core-concepts/branching", - "file": "docs/core-concepts/branching.mdx" - }, - { - "title": "Data anonymization", - "href": "/core-concepts/anonymization", - "file": "docs/core-concepts/anonymization.mdx" - }, - { - "title": "Schema changes", - "href": "/core-concepts/schema-changes", - "file": "docs/core-concepts/schema-changes.mdx" - } - ] - }, { "title": "Command-line Interface", "href": "/cli",