postbasejs
v0.3.6
Published
The official JavaScript client for Postbase — self-hosted backend as a service
Downloads
679
Maintainers
Readme
postbasejs
The official JavaScript/TypeScript client for Postbase — a self-hosted, open-source backend as a service.
What is Postbase?
Postbase is a self-hosted backend platform built on PostgreSQL. It gives you a database with a REST query API, authentication (password, magic link, OAuth), file storage, and row-level security — all running on your own infrastructure.
postbasejs is the client SDK for interacting with your Postbase instance from JavaScript or TypeScript apps.
Screenshots
Installation
npm install postbasejs
# or
pnpm add postbasejs
# or
yarn add postbasejsQuick Start
import { createClient } from 'postbasejs'
const postbase = createClient(
'https://your-postbase-instance.com',
'pb_anon_your_api_key',
{ projectId: 'your-project-id' }
)Your URL, anon key, and project ID can be found in the API Keys section of your Postbase dashboard.
Database
Query your PostgreSQL tables with a fluent, chainable API.
Select
// Fetch all posts
const { data, error } = await postbase.from('posts').select('*')
// Select specific columns
const { data } = await postbase.from('posts').select('id, title, created_at')
// With filters
const { data } = await postbase
.from('posts')
.select('*')
.eq('status', 'published')
.order('created_at', { ascending: false })
.limit(10)
// Get total count
const { data, count } = await postbase
.from('posts')
.select('*', { count: 'exact' })Filter operators
| Method | SQL equivalent |
|---|---|
| .eq(col, val) | col = val |
| .neq(col, val) | col != val |
| .gt(col, val) | col > val |
| .gte(col, val) | col >= val |
| .lt(col, val) | col < val |
| .lte(col, val) | col <= val |
| .like(col, pattern) | col LIKE pattern |
| .ilike(col, pattern) | col ILIKE pattern |
| .in(col, values) | col IN (values) |
| .is(col, null) | col IS NULL |
| .contains(col, val) | col @> val |
| .overlaps(col, val) | col && val |
| .textSearch(col, query) | full-text search |
| .or(filters) | col = val OR col = val |
| .not(col, op, val) | NOT col op val |
Insert
const { data, error } = await postbase
.from('posts')
.insert({ title: 'Hello World', status: 'draft' })
.select()
.single()Update
const { data, error } = await postbase
.from('posts')
.update({ status: 'published' })
.eq('id', 'post-id')
.select()
.single()Upsert
const { data, error } = await postbase
.from('profiles')
.upsert({ id: 'user-id', username: 'alice' }, { onConflict: 'id' })
.select()Delete
const { error } = await postbase
.from('posts')
.delete()
.eq('id', 'post-id')Single row helpers
// Errors if not exactly one row
const { data, error } = await postbase.from('posts').select('*').eq('id', id).single()
// Returns null if not found (no error)
const { data } = await postbase.from('posts').select('*').eq('id', id).maybeSingle()Pagination
// Limit + offset
const { data } = await postbase.from('posts').select('*').limit(20).offset(40)
// Range (inclusive)
const { data } = await postbase.from('posts').select('*').range(0, 19)Authentication
Sign up
const { data, error } = await postbase.auth.signUp({
email: '[email protected]',
password: 'supersecret',
})
// data.user, data.sessionSign in with password
const { data, error } = await postbase.auth.signInWithPassword({
email: '[email protected]',
password: 'supersecret',
})OTP & Magic Link (passwordless)
Magic Link:
const { error } = await postbase.auth.signInWithOtp({
email: '[email protected]',
type: 'magic_link', // optional, defaults to 'magic_link'
options: { redirectTo: 'https://yourapp.com/dashboard' },
})6-digit OTP Code:
// 1. Request the code
const { error } = await postbase.auth.signInWithOtp({
email: '[email protected]',
type: 'otp',
})
// 2. Verify the code
const { data, error } = await postbase.auth.verifyOtp({
email: '[email protected]',
token: '123456', // 6-digit code from email
})
// data.user, data.sessionOAuth (browser redirect)
await postbase.auth.signInWithOAuth({
provider: 'google', // or 'github', 'discord', etc.
options: { redirectTo: 'https://yourapp.com/callback' },
})Get current user
const { data: { user }, error } = await postbase.auth.getUser()Get current session
const { data: { session }, error } = await postbase.auth.getSession()
// session.accessToken, session.user, session.expiresAtSign out
await postbase.auth.signOut()Update user
const { data, error } = await postbase.auth.updateUser({
name: 'Alice',
metadata: { plan: 'pro' },
})Listen to auth state changes
const { data: { subscription } } = postbase.auth.onAuthStateChange((event, session) => {
// event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED'
console.log(event, session)
})
// Cleanup
subscription.unsubscribe()Admin (service role key required)
const adminClient = createClient(url, 'pb_service_your_service_key', { projectId: 'your-project-id' })
// List users
const { data } = await adminClient.auth.admin.listUsers({ page: 1, perPage: 50 })
// Create user
const { data } = await adminClient.auth.admin.createUser({
email: '[email protected]',
password: 'password',
email_confirm: true,
})
// Update user
await adminClient.auth.admin.updateUserById(userId, { email: '[email protected]' })
// Delete user
await adminClient.auth.admin.deleteUser(userId)Storage
Upload a file
const { data, error } = await postbase
.storage
.from('avatars')
.upload('user-123.png', file, { contentType: 'image/png' })
// data.path, data.fullPathGet public URL
const { data: { publicUrl } } = postbase
.storage
.from('avatars')
.getPublicUrl('user-123.png')Download a file
const { data: blob, error } = await postbase
.storage
.from('avatars')
.download('user-123.png')Create a signed URL (temporary access)
const { data, error } = await postbase
.storage
.from('private-docs')
.createSignedUrl('report.pdf', 3600) // expires in 1 hour
// data.signedUrlList files
const { data, error } = await postbase
.storage
.from('avatars')
.list('folder/', { limit: 100, sortBy: { column: 'name', order: 'asc' } })Delete files
const { error } = await postbase
.storage
.from('avatars')
.remove(['user-123.png', 'user-456.png'])Move / Copy
await postbase.storage.from('docs').move('old-name.pdf', 'new-name.pdf')
await postbase.storage.from('docs').copy('template.pdf', 'copy.pdf')Bucket management
// Create
await postbase.storage.createBucket('avatars', {
public: true,
fileSizeLimit: 5 * 1024 * 1024, // 5 MB
allowedMimeTypes: ['image/png', 'image/jpeg'],
})
// List
const { data: buckets } = await postbase.storage.listBuckets()
// Update
await postbase.storage.updateBucket('avatars', { public: false })
// Delete
await postbase.storage.deleteBucket('avatars')
// Empty (delete all objects)
await postbase.storage.emptyBucket('avatars')RPC (PostgreSQL functions)
Call a stored procedure or function in your project's schema:
const { data, error } = await postbase.rpc('get_nearby_posts', {
lat: 37.7749,
lng: -122.4194,
radius: 10,
})SSR (Server-Side Rendering)
For Next.js App Router, SvelteKit, Nuxt, or any SSR framework, import from postbasejs/ssr. This forwards the user's session cookie to Postbase so that RLS policies apply server-side.
# No extra install needed — it's included in postbasejsNext.js App Router
Server Component:
import { createServerClient } from 'postbasejs/ssr'
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const postbase = createServerClient(
process.env.NEXT_PUBLIC_POSTBASE_URL!,
process.env.NEXT_PUBLIC_POSTBASE_ANON_KEY!,
{
projectId: process.env.NEXT_PUBLIC_POSTBASE_PROJECT_ID!,
cookies: {
getAll: () => cookieStore.getAll(),
setAll: () => {}, // read-only in server components
},
}
)
const { data: posts } = await postbase.from('posts').select('*')
return <ul>{posts?.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}Middleware (session refresh):
// middleware.ts
import { createServerClient } from 'postbasejs/ssr'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const postbase = createServerClient(
process.env.NEXT_PUBLIC_POSTBASE_URL!,
process.env.NEXT_PUBLIC_POSTBASE_ANON_KEY!,
{
projectId: process.env.NEXT_PUBLIC_POSTBASE_PROJECT_ID!,
cookies: {
getAll: () => req.cookies.getAll(),
setAll: (cookies) =>
cookies.forEach(c => res.cookies.set(c.name, c.value, c.options as any)),
},
}
)
await postbase.auth.getSession() // refreshes token if needed
return res
}Client Component:
'use client'
import { createBrowserClient } from 'postbasejs/ssr'
const postbase = createBrowserClient(
process.env.NEXT_PUBLIC_POSTBASE_URL!,
process.env.NEXT_PUBLIC_POSTBASE_ANON_KEY!,
{ projectId: process.env.NEXT_PUBLIC_POSTBASE_PROJECT_ID! }
)TypeScript
The SDK is fully typed. Pass your row type as a generic for full IntelliSense:
interface Post {
id: string
title: string
status: 'draft' | 'published'
created_at: string
}
const { data } = await postbase.from<Post>('posts').select('*').eq('status', 'published')
// data is Post[] | nullRow Level Security (RLS)
When a user is signed in, their session JWT is automatically forwarded with every query. Your RLS policies can reference the user via:
current_setting('postbase.user_id', true) -- the authenticated user's ID
current_setting('postbase.role', true) -- the user's roleExample policy — users can only read their own rows:
CREATE POLICY "own rows" ON posts
FOR SELECT USING (
user_id = current_setting('postbase.user_id', true)::uuid
);Environment Variables
We recommend storing your Postbase credentials in environment variables:
NEXT_PUBLIC_POSTBASE_URL=https://your-postbase-instance.com
NEXT_PUBLIC_POSTBASE_ANON_KEY=pb_anon_...
NEXT_PUBLIC_POSTBASE_PROJECT_ID=your-project-idUse your service role key (pb_service_...) only in server-side code — it bypasses RLS.
License
MIT — see LICENSE.
Built with love by the Postbase team.
