API Package Overview
Complete guide to the @repo/api package for Appwrite integration in the BISO Sites monorepo.
API Package Overview
The @repo/api package provides type-safe Appwrite client wrappers for both server-side and client-side usage. It's the central package for all database, authentication, storage, and function interactions.
When to use this package
- Whenever you need to talk to Appwrite (database, storage, auth) from web, admin, or docs apps.
- When you want session-aware clients in Server Components, server actions, or API routes.
- To avoid duplicating low-level Appwrite client setup, especially around API keys and cookie handling.
Use createSessionClient() inside components/actions that run in response to user requests. Use createAdminClient() sparingly for webhooks or automation where you need elevated privileges.
Installation
bun add @repo/apiAlready listed as a workspace dependency; install it in new packages or scripts if needed.
Where it's used
| Surface | Usage |
|---|---|
| Web App server actions | Reads/writes memberships, orders, events |
| Admin App dashboards | Powers CRUD flows for users, posts, products |
| Docs search/API routes | Securely queries Appwrite for index data |
What's Included
The API package exports:
- Server clients - For Server Components, Server Actions, and API Routes
- Client exports - For browser/client component usage
- Storage helpers - URL generation utilities
- Type definitions - Full TypeScript types for your database schema
Package Structure
packages/api/
├── server.ts # Server-side clients (Next.js)
├── client.ts # Client-side exports (browser)
├── storage.ts # Storage URL helpers
├── types/
│ └── appwrite.ts # Generated TypeScript types
├── index.ts # Main exports
├── package.json
└── tsconfig.jsonQuick Start
Server-Side (Server Components & Actions)
import { createSessionClient } from '@repo/api/server';
// In a Server Component or Server Action
export default async function Page() {
const { account, db } = await createSessionClient();
// Get current user
const user = await account.get();
// Query database
const posts = await db.listDocuments('database_id', 'posts');
return <div>{/* ... */}</div>;
}Client-Side (Client Components)
'use client';
import { Client, Account, Databases } from '@repo/api/client';
export function MyComponent() {
const client = new Client()
.setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
.setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!);
const account = new Account(client);
const databases = new Databases(client);
// Use account and databases...
}Core Concepts
Session Client vs Admin Client
The package provides two types of server clients:
Session Client
- Use for: User-authenticated requests
- Reads: User's session cookie
- Permissions: Limited to user's permissions
- Common in: Web and admin apps
const { account, db, storage } = await createSessionClient();Admin Client
- Use for: Administrative operations, webhooks
- Authentication: API key (server-side only!)
- Permissions: Full database access
- Common in: API routes, webhooks, system tasks
const { account, db, storage, users } = await createAdminClient();Never expose the admin client or API key to the browser! Only use it in Server Components, Server Actions, or API Routes.
Available Services
Both createSessionClient() and createAdminClient() return:
| Service | Type | Purpose |
|---|---|---|
account | Account | User authentication and account management |
db | TablesDB | Database operations (CRUD) |
teams | Teams | Team management |
storage | Storage | File upload/download |
functions | Functions | Execute Appwrite Functions |
messaging | Messaging | Send messages/notifications |
Additionally, createAdminClient() provides:
| Service | Type | Purpose |
|---|---|---|
users | Users | User management (create, update, delete users) |
TypeScript Types
The package includes auto-generated types for your database schema:
import type { Users, Posts, Orders } from '@repo/api/types/appwrite';
// Fully typed database documents
const user: Users = await db.getDocument('users', userId);
const posts: Posts[] = await db.listDocuments('posts');Available Types
The package exports types for all database collections including:
Users- User profilesOrders- E-commerce ordersPayments- Payment recordsMemberships- Membership dataEvents,News,Jobs- Content typesWebshopProducts- Product catalogPages,PageTranslations- CMS pages- And many more...
See the Types Documentation for complete type reference.
Storage Utilities
Helper functions for generating Appwrite storage URLs:
import {
getStorageFileUrl,
getStorageFileDownloadUrl,
getStorageFileThumbnailUrl
} from '@repo/api';
// View file
const viewUrl = getStorageFileUrl('bucket_id', 'file_id');
// Download file
const downloadUrl = getStorageFileDownloadUrl('bucket_id', 'file_id');
// Image thumbnail
const thumbnailUrl = getStorageFileThumbnailUrl('bucket_id', 'file_id', {
width: 400,
height: 300,
quality: 90
});Usage Patterns
Query Data in Server Component
// app/posts/page.tsx
import { createSessionClient } from '@repo/api/server';
export default async function PostsPage() {
const { db } = await createSessionClient();
const posts = await db.listDocuments(
'database_id',
'posts_collection',
[
Query.equal('status', 'published'),
Query.orderDesc('$createdAt'),
Query.limit(10)
]
);
return (
<div>
{posts.documents.map(post => (
<PostCard key={post.$id} post={post} />
))}
</div>
);
}Mutate Data with Server Action
// app/actions/posts.ts
'use server';
import { createSessionClient } from '@repo/api/server';
import { ID } from 'node-appwrite';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const { db, account } = await createSessionClient();
// Verify authentication
const user = await account.get();
// Create document
const post = await db.createDocument(
'database_id',
'posts_collection',
ID.unique(),
{
title: formData.get('title'),
content: formData.get('content'),
authorId: user.$id,
status: 'draft',
}
);
revalidatePath('/posts');
return { success: true, postId: post.$id };
}Handle Webhook with Admin Client
// app/api/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createAdminClient } from '@repo/api/server';
export async function POST(request: NextRequest) {
// Verify webhook signature
const signature = request.headers.get('x-webhook-signature');
if (!verifySignature(signature)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Use admin client (no user session)
const { db } = await createAdminClient();
const data = await request.json();
await db.createDocument('webhooks', ID.unique(), data);
return NextResponse.json({ success: true });
}Client-Side Real-time Updates
'use client';
import { useEffect, useState } from 'react';
import { Client, Databases } from '@repo/api/client';
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 changes
const unsubscribe = databases.subscribe(
'database_id.posts_collection',
(response) => {
console.log('Update received:', response.payload);
// Update posts list
}
);
return () => unsubscribe();
}, []);
return <div>{/* Render posts */}</div>;
}File Upload
'use server';
import { createSessionClient } from '@repo/api/server';
import { ID } from 'node-appwrite';
import { getStorageFileUrl } from '@repo/api';
export async function uploadImage(formData: FormData) {
const file = formData.get('file') as File;
const { storage } = await createSessionClient();
// Upload file
const uploaded = await storage.createFile(
'images_bucket',
ID.unique(),
file
);
// Get URL
const url = getStorageFileUrl('images_bucket', uploaded.$id);
return { fileId: uploaded.$id, url };
}Query Helpers
The package re-exports Appwrite's Query helper for building queries:
import { Query } from '@repo/api';
// Equal
Query.equal('status', 'published')
// Not equal
Query.notEqual('status', 'draft')
// Greater than / Less than
Query.greaterThan('price', 100)
Query.lessThan('stock', 10)
// Search
Query.search('title', 'hello')
// Order
Query.orderAsc('createdAt')
Query.orderDesc('price')
// Limit & Offset
Query.limit(20)
Query.offset(40)
// Combine queries
await db.listDocuments('database', 'posts', [
Query.equal('status', 'published'),
Query.greaterThan('views', 100),
Query.orderDesc('createdAt'),
Query.limit(10)
]);Environment Variables
Required environment variables:
# Public (client-side accessible)
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://your-appwrite.com/v1
NEXT_PUBLIC_APPWRITE_PROJECT=your-project-id
# Server-only (never expose to client)
APPWRITE_API_KEY=your-api-keyNEXT_PUBLIC_*vars are exposed to the browserAPPWRITE_API_KEYmust NEVER be exposed (server-only)- Admin client requires
APPWRITE_API_KEY
Error Handling
Always handle Appwrite errors:
'use server';
import { createSessionClient } from '@repo/api/server';
import { AppwriteException } from 'node-appwrite';
export async function getUser() {
try {
const { account } = await createSessionClient();
const user = await account.get();
return { success: true, user };
} catch (error) {
if (error instanceof AppwriteException) {
console.error('Appwrite error:', error.code, error.message);
if (error.code === 401) {
return { success: false, error: 'Not authenticated' };
}
}
return { success: false, error: 'Unknown error' };
}
}Best Practices
Prefer Server Components for Data Fetching
// ✅ Good: Server Component
async function PostsList() {
const { db } = await createSessionClient();
const posts = await db.listDocuments(...);
return <div>{posts.map(...)}</div>;
}
// ❌ Avoid: Client Component with useEffect
'use client';
function PostsList() {
const [posts, setPosts] = useState([]);
useEffect(() => { fetch(...) }, []);
return <div>{posts.map(...)}</div>;
}Use Session Client for User Actions
// ✅ Good: Respects user permissions
const { db } = await createSessionClient();
await db.createDocument(...); // Uses user's permissions
// ❌ Wrong: Admin client for user actions
const { db } = await createAdminClient();
await db.createDocument(...); // Bypasses permissions!Use Admin Client Only When Necessary
// ✅ Good: Webhook with admin client
export async function POST(request: NextRequest) {
const { db } = await createAdminClient(); // No user session
await db.createDocument(...);
}
// ✅ Good: Session client for user request
export default async function Page() {
const { db } = await createSessionClient(); // User session
const data = await db.listDocuments(...);
}Always Check Authentication
'use server';
export async function protectedAction() {
const { account } = await createSessionClient();
try {
const user = await account.get();
// User is authenticated, proceed
} catch {
// Not authenticated
return { error: 'Please log in' };
}
}Revalidate After Mutations
'use server';
import { revalidatePath } from 'next/cache';
export async function updatePost(postId: string, data: any) {
const { db } = await createSessionClient();
await db.updateDocument('posts', postId, data);
// ✅ Revalidate to show updated data
revalidatePath('/posts');
revalidatePath(`/posts/${postId}`);
}Next Steps
- Server API Reference - Complete server client guide
- Client API Reference - Browser usage guide
- Storage Utilities - File handling
- Database Guide - Working with Appwrite database
- Authentication Guide - Auth patterns
