Client API Reference
Complete reference for browser-side Appwrite usage in BISO Sites.
Client API Reference
Client-side Appwrite exports for use in Client Components and browser JavaScript.
Overview
The @repo/api/client package re-exports Appwrite SDK classes for browser usage. These are used in Client Components that need to interact with Appwrite directly from the browser.
For most cases, prefer Server Components and Server Actions. Use client-side APIs only when necessary (real-time updates, client-side interactivity).
Exports
import {
Client,
Account,
Databases,
Storage,
Functions,
Query,
ID,
OAuthProvider,
Realtime
} from '@repo/api/client';
// Types (type-only imports)
import type { Models, RealtimeResponseEvent, Payload } from '@repo/api/client';Pre-configured Clients
For convenience, the package provides pre-configured client instances:
import {
clientSideClient,
clientDatabase,
clientStorage,
clientAccount,
clientFunctions
} from '@repo/api/client';
// Use directly
const user = await clientAccount.get();
const posts = await clientDatabase.listDocuments('db_id', 'posts');Basic Setup
Manual Client Setup
'use client';
import { Client, Account, Databases } from '@repo/api/client';
export function MyComponent() {
// Create client
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!);
// Initialize services
const account = new Account(client);
const databases = new Databases(client);
// Use services...
}Using Pre-configured Client
'use client';
import { clientAccount, clientDatabase } from '@repo/api/client';
export function MyComponent() {
// Already configured, ready to use
const fetchUser = async () => {
const user = await clientAccount.get();
console.log(user);
};
}Services
Account
Authentication and user management from the browser.
'use client';
import { clientAccount } from '@repo/api/client';
import { useState } from 'react';
export function LoginForm() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Note: Actual authentication is handled server-side
// This is a simplified example of client-side interaction
const response = await fetch('/api/auth/send-magic-link', {
method: 'POST',
body: JSON.stringify({ email }),
});
if (response.ok) {
setMessage('Check your email for login link!');
}
} catch (error) {
console.error('Login failed:', error);
}
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
{message && <p>{message}</p>}
<button type="submit">Login</button>
</form>
);
}Common Methods:
// Get current user
const user = await clientAccount.get();
// Logout
await clientAccount.deleteSession('current');
// Update preferences
await clientAccount.updatePrefs({ theme: 'dark' });
// Get sessions
const sessions = await clientAccount.listSessions();BISO Sites uses server-side authentication (Magic Link and OAuth). Authentication is handled via server actions and API routes, not directly from client components. See the Authentication Guide for implementation details.
Databases
Query and mutate data from the browser.
'use client';
import { clientDatabase, Query } from '@repo/api/client';
import { useEffect, useState } from 'react';
export function PostsList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
const result = await clientDatabase.listDocuments(
'database_id',
'posts',
[
Query.equal('status', 'published'),
Query.orderDesc('$createdAt'),
Query.limit(10)
]
);
setPosts(result.documents);
};
fetchPosts();
}, []);
return (
<div>
{posts.map(post => (
<div key={post.$id}>{post.title}</div>
))}
</div>
);
}Common Methods:
// List documents
const result = await clientDatabase.listDocuments(
'database_id',
'collection_id',
[Query.limit(10)]
);
// Get document
const doc = await clientDatabase.getDocument(
'database_id',
'collection_id',
'document_id'
);
// Create document
const doc = await clientDatabase.createDocument(
'database_id',
'collection_id',
ID.unique(),
{ title: 'Hello', content: 'World' }
);
// Update document
await clientDatabase.updateDocument(
'database_id',
'collection_id',
'document_id',
{ title: 'Updated' }
);
// Delete document
await clientDatabase.deleteDocument(
'database_id',
'collection_id',
'document_id'
);Storage
File operations from the browser.
'use client';
import { clientStorage, ID } from '@repo/api/client';
import { getStorageFileUrl } from '@repo/api';
export function FileUpload() {
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
try {
// Upload file
const uploaded = await clientStorage.createFile(
'bucket_id',
ID.unique(),
file
);
// Get URL
const url = getStorageFileUrl('bucket_id', uploaded.$id);
console.log('File uploaded:', url);
} catch (error) {
console.error('Upload failed:', error);
}
};
return <input type="file" onChange={handleUpload} />;
}Common Methods:
// Upload file
const file = await clientStorage.createFile(
'bucket_id',
ID.unique(),
fileBlob
);
// Get file info
const fileInfo = await clientStorage.getFile('bucket_id', 'file_id');
// List files
const files = await clientStorage.listFiles('bucket_id');
// Delete file
await clientStorage.deleteFile('bucket_id', 'file_id');
// Get file view URL (use helper instead)
// const url = clientStorage.getFileView('bucket_id', 'file_id');Realtime
Subscribe to real-time updates.
'use client';
import { Client, Databases } from '@repo/api/client';
import { useEffect, useState } from 'react';
export function LivePosts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!);
const databases = new Databases(client);
// Subscribe to collection changes
const unsubscribe = client.subscribe(
'databases.database_id.collections.posts.documents',
(response) => {
console.log('Update received:', response.payload);
// Handle different event types
if (response.events.includes('databases.*.collections.*.documents.*.create')) {
setPosts(prev => [...prev, response.payload]);
}
if (response.events.includes('databases.*.collections.*.documents.*.update')) {
setPosts(prev => prev.map(post =>
post.$id === response.payload.$id ? response.payload : post
));
}
if (response.events.includes('databases.*.collections.*.documents.*.delete')) {
setPosts(prev => prev.filter(post => post.$id !== response.payload.$id));
}
}
);
return () => unsubscribe();
}, []);
return (
<div>
{posts.map(post => (
<div key={post.$id}>{post.title}</div>
))}
</div>
);
}Subscription Channels:
// All documents in a collection
client.subscribe('databases.{databaseId}.collections.{collectionId}.documents');
// Specific document
client.subscribe('databases.{databaseId}.collections.{collectionId}.documents.{documentId}');
// All files in a bucket
client.subscribe('buckets.{bucketId}.files');
// Specific file
client.subscribe('buckets.{bucketId}.files.{fileId}');
// Account changes
client.subscribe('account');React Hooks Pattern
Common pattern for using Appwrite in React:
'use client';
import { clientDatabase, Query } from '@repo/api/client';
import { useEffect, useState } from 'react';
export function usePosts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPosts = async () => {
try {
const result = await clientDatabase.listDocuments(
'database_id',
'posts',
[Query.orderDesc('$createdAt')]
);
setPosts(result.documents);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchPosts();
}, []);
return { posts, loading, error };
}
// Usage
export function PostsList() {
const { posts, loading, error } = usePosts();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{posts.map(post => (
<div key={post.$id}>{post.title}</div>
))}
</div>
);
}OAuth Authentication
'use client';
import { clientAccount, OAuthProvider } from '@repo/api/client';
export function SocialLogin() {
const handleGoogleLogin = () => {
clientAccount.createOAuth2Session(
OAuthProvider.Google,
'http://localhost:3000/auth/callback', // Success URL
'http://localhost:3000/login' // Failure URL
);
};
const handleGitHubLogin = () => {
clientAccount.createOAuth2Session(
OAuthProvider.Github,
'http://localhost:3000/auth/callback',
'http://localhost:3000/login'
);
};
return (
<div>
<button onClick={handleGoogleLogin}>Login with Google</button>
<button onClick={handleGitHubLogin}>Login with GitHub</button>
</div>
);
}Error Handling
'use client';
import { clientAccount } from '@repo/api/client';
import { AppwriteException } from 'appwrite';
export function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
const currentUser = await clientAccount.get();
setUser(currentUser);
} catch (error) {
if (error instanceof AppwriteException) {
// Handle specific Appwrite errors
switch (error.code) {
case 401:
console.error('Not authenticated');
break;
case 429:
console.error('Too many requests');
break;
default:
console.error('Error:', error.message);
}
} else {
console.error('Unknown error:', error);
}
}
};
fetchUser();
}, []);
if (!user) return <div>Loading...</div>;
return <div>Welcome, {user.name}</div>;
}Query Helper
import { Query } from '@repo/api/client';
// Build queries
const queries = [
Query.equal('status', 'published'),
Query.greaterThan('views', 100),
Query.search('title', 'react'),
Query.orderDesc('$createdAt'),
Query.limit(20),
Query.offset(0)
];
const result = await clientDatabase.listDocuments(
'database_id',
'posts',
queries
);ID Helper
import { ID } from '@repo/api/client';
// Generate unique ID
const uniqueId = ID.unique();
// Use custom ID
const customId = ID.custom('my-custom-id');Best Practices
Prefer Server Components
// ✅ Better: Server Component
async function Posts() {
const { db } = await createSessionClient();
const posts = await db.listDocuments(...);
return <div>{posts.map(...)}</div>;
}
// ❌ Avoid: Client Component unless necessary
'use client';
function Posts() {
const [posts, setPosts] = useState([]);
useEffect(() => { /* fetch */ }, []);
return <div>{posts.map(...)}</div>;
}Handle Loading States
'use client';
export function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
clientDatabase.listDocuments(...)
.then(result => setPosts(result.documents))
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Loading...</div>;
return <div>{/* render posts */}</div>;
}Clean Up Subscriptions
useEffect(() => {
const unsubscribe = client.subscribe(channel, callback);
// ✅ Clean up on unmount
return () => unsubscribe();
}, []);Use Environment Variables
// ✅ Good: Use env vars
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!);
// ❌ Bad: Hardcoded values
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject('abc123');When to Use Client API
Use client-side APIs when:
✅ Real-time updates needed ✅ Client-side interactivity required ✅ OAuth authentication flows ✅ File uploads with progress tracking ✅ Dynamic, user-specific data that can't be pre-rendered
Avoid client-side APIs when:
❌ SEO is important (use Server Components) ❌ Data can be fetched at build/request time ❌ No real-time updates needed ❌ Server Actions can handle the mutation
Next Steps
- Server API Reference - Server-side usage
- Storage Utilities - File handling helpers
- Authentication Guide - Auth patterns
