# Check auth endpoint status
Source: https://docs.basic.tech/api-reference/auth/check-auth-endpoint-status
get /auth/
# Get auth token
Source: https://docs.basic.tech/api-reference/auth/get-auth-token
post /auth/token
# Get user info
Source: https://docs.basic.tech/api-reference/auth/get-user-info
get /auth/userInfo
This endpoint is deprecated. Use /auth/userinfo instead.
# Redirect to sign in
Source: https://docs.basic.tech/api-reference/auth/redirect-to-sign-in
get /auth/authorize
# Create item
Source: https://docs.basic.tech/api-reference/pds/create-item-pds
post /account/{project_id}/db/{table}
# Update an item
Source: https://docs.basic.tech/api-reference/pds/delete-an-item-pds
delete /account/{project_id}/db/{table}/{item_id}
# Get all items
Source: https://docs.basic.tech/api-reference/pds/get-all-items-pds
get /account/{project_id}/db/{table}
# Get specific item in a table
Source: https://docs.basic.tech/api-reference/pds/get-specific-item-pds
get /account/{project_id}/db/{table}/{item_id}
# Replace an item
Source: https://docs.basic.tech/api-reference/pds/replace-an-item-pds
put /account/{project_id}/db/{table}/{item_id}
# Update an item
Source: https://docs.basic.tech/api-reference/pds/update-an-item-pds
patch /account/{project_id}/db/{table}/{item_id}
# Create new API key
Source: https://docs.basic.tech/api-reference/project-keys/create-new-api-key
post /project/{id}/key
Creates a new API key for the project with specified permissions
# Delete API key
Source: https://docs.basic.tech/api-reference/project-keys/delete-api-key
delete /project/{id}/key/{key_id}
Permanently deletes an API key from the project. This action cannot be undone.
# Get all keys for a project
Source: https://docs.basic.tech/api-reference/project-keys/get-all-keys
get /project/{id}/key
Retrieves all API keys associated with the project
# Get specific API key
Source: https://docs.basic.tech/api-reference/project-keys/get-specific-api-key
get /project/{id}/key/{key_id}
Retrieves details of a specific API key for the project
# Regenerate API key
Source: https://docs.basic.tech/api-reference/project-keys/regenerate-api-key
post /project/{id}/key/{key_id}/regenerate
Regenerates an existing API key with a new value while maintaining the same permissions and label
# Update API key
Source: https://docs.basic.tech/api-reference/project-keys/update-api-key
patch /project/{id}/key/{key_id}
Updates the label and/or roles of an existing API key
# Verify API key
Source: https://docs.basic.tech/api-reference/project-keys/verify-api-key
post /project/{id}/key/verify
Verifies if an API key is valid for the project and returns its associated permissions
# Create item
Source: https://docs.basic.tech/api-reference/project-users/create-item
post /project/{id}/user/{user_id}/db/{table_id}
# Delete a user from the project
Source: https://docs.basic.tech/api-reference/project-users/delete-a-user-from-the-project
delete /project/{id}/user/{user_id}
# Delete an item
Source: https://docs.basic.tech/api-reference/project-users/delete-an-item
delete /project/{id}/user/{user_id}/db/{table_id}/{item_id}
# Get all items in a table
Source: https://docs.basic.tech/api-reference/project-users/get-all-items
get /project/{id}/user/{user_id}/db/{table_id}
# Get all users in project
Source: https://docs.basic.tech/api-reference/project-users/get-project-users
get /project/{id}/user/
Retrieves all connected users for the project
# Get specific item in a table
Source: https://docs.basic.tech/api-reference/project-users/get-specific-item
get /project/{id}/user/{user_id}/db/{table_id}/{item_id}
# Get user details
Source: https://docs.basic.tech/api-reference/project-users/get-user-details
get /project/{id}/user/{user_id}
Retrieves details for a specific user, fetching profile from their PDS via service token
# Replace an item
Source: https://docs.basic.tech/api-reference/project-users/replace-an-item
put /project/{id}/user/{user_id}/db/{table_id}/{item_id}
# Update an item
Source: https://docs.basic.tech/api-reference/project-users/update-an-item-in-the-users-database-1
patch /project/{id}/user/{user_id}/db/{table_id}/{item_id}
# Update user metadata
Source: https://docs.basic.tech/api-reference/project-users/update-user-details
patch /project/{id}/user/{user_id}
# Check project slug availability
Source: https://docs.basic.tech/api-reference/projects/check-project-slug-availability
get /project/slug
Checks if a project slug is available for use
# Create a new project
Source: https://docs.basic.tech/api-reference/projects/create-a-new-project
post /project/
Creates a new project for the authenticated user and returns an API key for the project
# Delete a project
Source: https://docs.basic.tech/api-reference/projects/delete-a-project
delete /project/{id}
Permanently deletes a project and all its associated data. This action cannot be undone.
# Get all admin projects of developer
Source: https://docs.basic.tech/api-reference/projects/get-all-admin-projects
get /project/
Retrieves all projects owned by the authenticated user
# Get project details
Source: https://docs.basic.tech/api-reference/projects/get-project-details
get /project/{id}
Retrieves detailed information for a specific project
# Get project schema
Source: https://docs.basic.tech/api-reference/projects/get-project-schema
get /project/{id}/schema
Retrieves the schema configuration for a specific project. Can return either the latest version, a specific version, or all versions.
# Merge and update project schema
Source: https://docs.basic.tech/api-reference/projects/patch-project-schema
patch /project/{id}/schema
Merges the provided schema with the existing schema and creates a new version.
# Update project details
Source: https://docs.basic.tech/api-reference/projects/update-project-details
patch /project/{id}
Updates the details of a specific project. Only provided fields will be updated.
# Update project schema
Source: https://docs.basic.tech/api-reference/projects/update-project-schema
post /project/{id}/schema
Updates the schema configuration for a specific project. Creates a new version of the schema.
# Upload project image
Source: https://docs.basic.tech/api-reference/projects/upload-project-image
post /project/{id}/upload-image
# Accept a team invite
Source: https://docs.basic.tech/api-reference/teams/accept-a-team-invite
post /team/accept-invite
Accepts a team invitation and adds the user to the team
# Check team slug availability
Source: https://docs.basic.tech/api-reference/teams/check-team-slug-availability
get /team/slug
Checks if a team slug is available for use
# Create a new team
Source: https://docs.basic.tech/api-reference/teams/create-a-new-team
post /team/
Creates a new team for the authenticated user
# Create a team invite
Source: https://docs.basic.tech/api-reference/teams/create-a-team-invite
post /team/{team_id}/invite
Creates a new invite for a team
# Delete a team invite
Source: https://docs.basic.tech/api-reference/teams/delete-a-team-invite
delete /team/{team_id}/invite/{invite_id}
Deletes a team invitation
# Delete a team member
Source: https://docs.basic.tech/api-reference/teams/delete-a-team-member
delete /team/{team_id}/member/{member_id}
Removes a member from a team
# Get team by ID
Source: https://docs.basic.tech/api-reference/teams/get-team-by-id
get /team/{team_id}
Retrieves a specific team by its ID
# Get team invites
Source: https://docs.basic.tech/api-reference/teams/get-team-invites
get /team/{team_id}/invite
Retrieves all pending invites for a specific team
# Get team members
Source: https://docs.basic.tech/api-reference/teams/get-team-members
get /team/{team_id}/member
Retrieves all members of a team
# Get user teams
Source: https://docs.basic.tech/api-reference/teams/get-user-teams
get /team/
Retrieves all teams the user is a member of
# Update a team
Source: https://docs.basic.tech/api-reference/teams/update-a-team
patch /team/{team_id}
Updates a team's name and/or slug
# Update a team member
Source: https://docs.basic.tech/api-reference/teams/update-a-team-member
patch /team/{team_id}/member/{member_id}
Updates a team member's roles or role_name
# Check utils endpoint status
Source: https://docs.basic.tech/api-reference/utils/check-utils-endpoint-status
get /utils/
# Compare two schemas
Source: https://docs.basic.tech/api-reference/utils/compare-two-schemas
post /utils/schema/compareSchema
Compare two schemas and return the differences
# Convert text to URL-friendly slug
Source: https://docs.basic.tech/api-reference/utils/convert-text-to-url-friendly-slug
get /utils/slugify
Converts any text into a URL-friendly slug by removing special characters, converting to lowercase, and replacing spaces with hyphens
# Verify update schema
Source: https://docs.basic.tech/api-reference/utils/verify-update-schema
post /utils/schema/verifyUpdateSchema
Verify update schema
# ⚡️ Quickstart
Source: https://docs.basic.tech/basic-expo-rn/basic-rn-sdk
Using the @basictech/expo library with your React Native project
## Installation
Install the package via npm:
```bash theme={null}
npm install @basictech/expo
```
## Define a schema
Define your database schema as a TypeScript constant (e.g., `src/schema.ts`):
```typescript theme={null}
// src/basic.config.ts
export const schema = {
project_id: 'YOUR_PROJECT_ID', // Replace with your actual project ID
version: 1,
tables: {
// example table for a notes app
notes: {
type: 'collection',
fields: {
title: { type: 'string', indexed: true },
content: { type: 'string' },
createdAt: { type: 'number', indexed: true },
completed: { type: 'boolean', indexed: true },
priority: { type: 'number', indexed: true },
tags: { type: 'json', indexed: true }
},
},
// Add other tables here
},
};
```
## Provider Integration
Wrap your application's root with the `BasicProvider`:
```tsx theme={null}
// App.tsx
import { BasicProvider } from '@basictech/expo';
import { schema } from './basic.config';
import MainApp from './MainApp';
export default function App() {
return (
);
}
```
`project_id` is inferred from `schema.project_id` on `BasicProvider`; passing `project_id` separately is deprecated.
**Note:** Set the correct URI scheme under `expo.scheme` in `app.json` so OAuth redirects return to your app.
## Authentication & State Management
Use the `useBasic` hook from `@basictech/expo` inside your components to access authentication state and database client:
```tsx theme={null}
import { useBasic } from '@basictech/expo';
const { user, signIn, signOut, db, isLoading, isSignedIn } = useBasic();
```
**Returned properties:**
* `user`: The authenticated user or null.
* `signIn()`: Starts the OAuth login flow.
* `signOut()`: Logs the user out, clearing all tokens.
* `db`: The main [database client](/sdk-reference/expo-rn).
* `isLoading`: True during authentication state checks.
* `isSignedIn`: Whether a user is authenticated.
# Set up your Basic account
Source: https://docs.basic.tech/basic-expo-rn/rn-adminportal
Using the admin portal to create a new project.
# Create your project
Head to [https://app.basic.tech](https://app.basic.tech) to begin creating your project.
Login with your Basic account. If you don't have one yet, signup to Basic.
After creating your Basic account, head to the Dashboard and create a new project.
Use the form to create a project with a unique name and slug. Make sure not to leave the name or slug fields blank.
This page allows you to view all your projects and manage them. To view project details, click into any of them.
You'll need these if you're using the API to connect to your database. Store them securely.
Keep your API Key a secret! Only use it on a secure server environment.
Make sure to replace `` and ``.
```js GET Project Details theme={null}
fetch("https://api.basic.tech/project/", {
headers: {
"Authorization": "Bearer ",
},
}).then(response => response.json())
.then(data => console.log(data));
```
# Create your schema
There are two ways to create your schema, either using the table UI, or by using the code editor. Let's explore the table UI first.
Give your table a unique name, and add fields to it. Fields need to have a unique name within each table, and you can use the dropdown to select the type of each field.
Fields can take on the following types: `string`, `number`, `boolean`, `json`.
You can also edit your schema with the code editor.
Once you've added your tables, you can publish your changes. Upon publishing, you'll notice your version number has been automatically incremented.
You may also discard changes if you'd like to revert to the existing version.
# Next steps
You can now start building your app!
If you are using a React app, we recommend you use the [React SDK](/basic-react/basic-react-sdk) to get started.
Otherwise, you can use the [API](/basic-restapi/basic-api) for any language.
# Filtering, ordering, pagination
Source: https://docs.basic.tech/basic-expo-rn/rn-filtering
Learn how to filter results using the Expo / RN SDK
## Querying
Chain after `.getAll()`:
### Filtering (`filter`)
```js theme={null}
const notes = await db.from('notes').getAll().filter({ completed: true });
```
* **Method:** `filter(conditions)`
* **Supported Operators:** eq, neq, gt, gte, lt, lte, like, ilike, in, not (see [list of operators](#supported-operators))
### Ordering (`order`)
```js theme={null}
const ordered = await db.from('notes').getAll().order('created_at', 'desc');
```
* **Method:** `order(field, direction?)` (`direction` defaults to 'asc')
### Pagination (`limit`, `offset`)
```js theme={null}
const paged = await db.from('notes').getAll().limit(10).offset(10);
```
* **Methods:** `limit(count)`, `offset(count)`
**Note:** All these must be chained after `.getAll()`.
## Supported operators
All used within `filter()`:
| Operator | Description | Example |
| -------- | -------------------------- | -------------------------------------------------- |
| `eq` | Equality | `.filter({ status: { eq: "active" } })` |
| `neq` | Not equal | `.filter({ status: { neq: "deleted" } })` |
| `gt` | Greater than | `.filter({ priority: { gt: 3 } })` |
| `gte` | Greater than or equal | `.filter({ priority: { gte: 3 } })` |
| `lt` | Less than | `.filter({ priority: { lt: 3 } })` |
| `lte` | Less than or equal | `.filter({ priority: { lte: 3 } })` |
| `like` | Pattern (case sensitive) | `.filter({ title: { like: "%project%" } })` |
| `ilike` | Pattern (case insensitive) | `.filter({ title: { ilike: "%todo%" } })` |
| `in` | Value in set | `.filter({ tags: { in: ["work", "important"] } })` |
| `not` | Negation | `.filter({ tags: { not: { eq: "work" } } })` |
## Limitations
* Only one filterable condition per field per query (no compound filters across multiple fields)
* Range filters (e.g., `{ gte: x, lte: y }`) not supported
* Method chaining order is flexible, but all must be chained after `getAll()`
# Schema
Source: https://docs.basic.tech/basic-expo-rn/rn-schema
Deeper dive into how schemas work in Basic
# Overview
In Basic, we declare schemas in a JSON format as part of the `basic.config.ts` or `basic.config.js` file that lives in your repository.
These `basic.config` files can be committed to git, and unlock "schema-as-code" capabilities for your project.
Basic schemas are very similar to SQL/Postgres tables with the following differences:
* no separate users table required (Basic's datastore per user model already ties data to each specific user)
* no need to declare RLS
* `id` and `created_at` fields are created automatically for you
Schemas would follow this general format:
The top level requires three parameters:
### project\_id
This is your project ID, which you can find in the Basic dashboard, or will be auto-generated for you if you had used the [CLI to create your project](/get-started/cli).
### version
This will be used for versioning your schema. It should be an integer that starts at 0 and increments by 1 for each new version.
# Tables
Let's double click into the tables to learn how they are declared in the schema.
Each table is declared as an object where the key is the table name and the value is an object with the following two parameters:
### type
This refers to the type of database table you are creating. At the moment, Basic only supports `collection`, which will operate similarly to MongoDB collections.
### fields
Fields are the columns of the table. They will be declared as an object of objects. Learn more about the fields below.
# Fields
Each field is declared as an object where the key is the field name and the value is an object with the following 2 parameters:
Default values for fields are not supported yet. Nesting fields is not supported; please use a flat structure for field declarations.
### type
This is the data type of the field. Basic supports the following field types: `string`, `number`, `boolean`, `json`.
### indexed
This lets you control if the field should be indexed in the user's client database for local-first benefits. For now, this should always be set to `true`.
# Example
When put all together, your schema could look like this:
```javascript basic.config.ts theme={null}
export const schema = {
project_id: '1234567890',
version: 0,
tables: {
comments: {
type: 'collection',
fields: {
title: {
type: 'string',
indexed: true
},
body: {
type: 'string',
indexed: true
},
rating: {
type: 'number',
indexed: true
}
}
},
items: {
type: 'collection',
fields: {
name: {
type: 'string',
indexed: true
},
quantity: {
type: 'number',
indexed: true
},
available: {
type: 'boolean',
indexed: true
}
}
}
}
}
```
# Quickstart: Next.js
Source: https://docs.basic.tech/basic-nextjs/basic-nextjs-sdk
Using the @basictech/nextjs library with your Next.js project
In this guide, you will:
1. Install the Basic Next.js SDK into an existing Next.js project
2. Connect to a Basic project and customize the schema
3. Add sign in and sign out functionality
This library is for Next.js applications using the App Router. For plain React apps, see the [@basictech/react documentation](/basic-react/basic-react-sdk).
# Installation
Navigate to your project and install the Basic Next.js SDK:
```bash Terminal theme={null}
cd your-nextjs-project
npm install @basictech/nextjs
```
# Set up basic.config
Create a new file called `basic.config.ts` in the root of your project:
```ts basic.config.ts theme={null}
export const schema = {
project_id: 'YOUR_PROJECT_ID',
version: 0,
tables: {
todos: {
type: 'collection',
fields: {
title: {
type: 'string',
indexed: true,
},
completed: {
type: 'boolean',
}
}
}
}
}
```
Replace `YOUR_PROJECT_ID` with your Project ID from the [Basic dashboard](https://admin.basic.tech).
# Create a Client Provider
Create `app/providers.tsx` to wrap your app with BasicProvider:
```tsx app/providers.tsx theme={null}
'use client'
import { BasicProvider } from '@basictech/nextjs/client'
import { schema } from '../basic.config'
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
The `'use client'` directive is required because BasicProvider uses React context and hooks.
Update `app/layout.tsx` to use the providers:
```tsx app/layout.tsx theme={null}
import { Providers } from './providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
```
# Adding Authentication
Create a component to handle authentication. This must be a client component:
```tsx app/components/AuthButton.tsx theme={null}
'use client'
import { useBasic } from '@basictech/nextjs/client'
export function AuthButton() {
const { isReady, isSignedIn, user, signIn, signOut } = useBasic()
if (!isReady) {
return
Loading...
}
if (!isSignedIn) {
return
}
return (
Signed in as: {user?.email}
)
}
```
Add the auth component to your page:
```tsx app/page.tsx theme={null}
import { AuthButton } from './components/AuthButton'
export default function Home() {
return (
My App
)
}
```
Clicking the sign in button will redirect to the Basic.id login page. After signing in, users are redirected back to your app.
***
# Important: Import Patterns
Due to Next.js SSR, you must use the correct import paths to avoid "self is not defined" errors.
| File Type | Import From |
| ---------------------------------- | ------------------------------------------------ |
| Client components (`'use client'`) | `@basictech/nextjs/client` or `@basictech/react` |
| Server components / middleware | `@basictech/nextjs` |
```tsx theme={null}
// ✅ Correct - in a client component
'use client'
import { useBasic, BasicProvider } from '@basictech/nextjs/client'
// ✅ Also correct - importing directly from react
'use client'
import { useBasic, BasicProvider } from '@basictech/react'
// ❌ Wrong - will cause SSR errors
import { useBasic } from '@basictech/nextjs' // Don't do this in client components!
```
***
# Using the Database
Once authenticated, you can use the database in your client components:
```tsx app/components/TodoList.tsx theme={null}
'use client'
import { useBasic, useQuery } from '@basictech/nextjs/client'
export function TodoList() {
const { db, isSignedIn } = useBasic()
// Subscribe to todos - automatically updates when data changes
const todos = useQuery(() => db.collection('todos').getAll())
const addTodo = async () => {
await db.collection('todos').add({
title: 'New todo',
completed: false
})
}
if (!isSignedIn) {
return
Please sign in to view todos
}
return (
{todos?.map((todo) => (
{todo.title}
))}
)
}
```
***
# Next Steps
* [Database Modes](/basic-nextjs/nextjs-modes) - Learn about sync vs remote mode
* [Middleware](/basic-nextjs/nextjs-middleware) - Protect routes with authentication
* [Database Methods](/sdk-reference/react-db) - Full database API reference
# Middleware: Route Protection
Source: https://docs.basic.tech/basic-nextjs/nextjs-middleware
Protect routes with authentication middleware in Next.js
The `@basictech/nextjs` package includes middleware utilities to protect routes and require authentication before accessing certain pages.
# Quick Setup
Create a `middleware.ts` file in your project root:
```ts middleware.ts theme={null}
import { createBasicMiddleware } from '@basictech/nextjs'
export const middleware = createBasicMiddleware({
protectedRoutes: ['/dashboard/*', '/settings/*', '/api/protected/*'],
publicRoutes: ['/login', '/signup', '/'],
signInUrl: '/login'
})
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
}
```
With this setup:
* Routes matching `/dashboard/*`, `/settings/*`, or `/api/protected/*` require authentication
* Unauthenticated users are redirected to `/login`
* The return URL is preserved so users can be redirected back after signing in
***
# Configuration Options
## createBasicMiddleware
The main function to create authentication middleware.
```ts theme={null}
createBasicMiddleware(config?: BasicMiddlewareConfig)
```
### Options
| Option | Type | Default | Description |
| --------------------- | ---------- | ---------------------------------- | ------------------------------------------------------------------------------ |
| `protectedRoutes` | `string[]` | `[]` | Routes that require authentication. Supports glob patterns like `/dashboard/*` |
| `publicRoutes` | `string[]` | `['/login', '/signup', '/auth/*']` | Routes that are always public (bypass auth check) |
| `signInUrl` | `string` | `'/login'` | Where to redirect unauthenticated users |
| `afterSignInUrl` | `string` | `'/'` | Where to redirect after successful sign-in |
| `tokenCookieName` | `string` | `'basic_access_token'` | Cookie name for the access token |
| `fullTokenCookieName` | `string` | `'basic_token'` | Cookie name for the full token object |
### Route Matching
Routes support simple glob patterns with `*` wildcard:
```ts theme={null}
{
protectedRoutes: [
'/dashboard/*', // Matches /dashboard, /dashboard/settings, etc.
'/api/user/*', // Matches /api/user, /api/user/profile, etc.
'/admin', // Matches exactly /admin
],
publicRoutes: [
'/auth/*', // Matches /auth/callback, /auth/error, etc.
'/', // Matches exactly /
]
}
```
***
# Alternative: Simple Auth Middleware
For simpler use cases, use `withBasicAuth` which applies default settings:
```ts middleware.ts theme={null}
import { withBasicAuth } from '@basictech/nextjs'
export const middleware = withBasicAuth
export const config = {
// Only run middleware on these routes
matcher: ['/dashboard/:path*', '/settings/:path*']
}
```
This redirects unauthenticated users to `/login` for any matched routes.
***
# Utility Functions
## getAuthFromRequest
Check authentication status from a request:
```ts theme={null}
import { getAuthFromRequest } from '@basictech/nextjs'
export function middleware(request: NextRequest) {
const { isAuthenticated, token } = getAuthFromRequest(request)
if (!isAuthenticated) {
// Handle unauthenticated request
}
// Use token for API calls if needed
}
```
## getReturnUrl
Get the return URL from search params (useful on login pages):
```ts theme={null}
import { getReturnUrl } from '@basictech/nextjs'
export function middleware(request: NextRequest) {
const returnUrl = getReturnUrl(request, '/')
// After sign-in, redirect to returnUrl
}
```
***
# Full Example
```ts middleware.ts theme={null}
import { createBasicMiddleware } from '@basictech/nextjs'
export const middleware = createBasicMiddleware({
// Protect dashboard and API routes
protectedRoutes: [
'/dashboard/*',
'/settings/*',
'/api/user/*',
'/api/protected/*'
],
// Keep these public
publicRoutes: [
'/',
'/login',
'/signup',
'/about',
'/api/public/*',
'/auth/*'
],
// Custom sign-in page
signInUrl: '/login',
// Redirect here after signing in (if no return URL)
afterSignInUrl: '/dashboard'
})
// Run middleware on all routes except static files
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)']
}
```
***
# Login Page Example
Create a login page that handles the return URL:
```tsx app/login/page.tsx theme={null}
'use client'
import { useBasic } from '@basictech/nextjs/client'
import { useSearchParams, useRouter } from 'next/navigation'
import { useEffect } from 'react'
export default function LoginPage() {
const { isReady, isSignedIn, signIn } = useBasic()
const searchParams = useSearchParams()
const router = useRouter()
const returnUrl = searchParams.get('returnUrl') || '/dashboard'
useEffect(() => {
// If already signed in, redirect to return URL
if (isReady && isSignedIn) {
router.push(returnUrl)
}
}, [isReady, isSignedIn, returnUrl, router])
if (!isReady) {
return
Loading...
}
return (
Sign In
Sign in to access your dashboard
)
}
```
# Database Modes: Sync vs Remote
Source: https://docs.basic.tech/basic-nextjs/nextjs-modes
Understanding the two database modes in the Basic SDK
The Basic SDK supports two database modes that determine how data is stored and synchronized. Choose the mode that best fits your application's needs.
# Overview
| Feature | Sync Mode (Default) | Remote Mode |
| ----------------------- | ------------------- | ------------------ |
| Local Storage | IndexedDB | None |
| Offline Support | Yes | No |
| Real-time Updates | Yes (WebSocket) | No |
| `useQuery` Auto-refresh | Yes | No |
| SSR Compatible | Partial | Yes |
| Authentication Required | For sync only | For all operations |
***
# Sync Mode (Default)
Sync mode provides a local-first experience with automatic synchronization to the cloud.
```tsx app/providers.tsx theme={null}
'use client'
import { BasicProvider } from '@basictech/nextjs/client'
import { schema } from '../basic.config'
export function Providers({ children }: { children: React.ReactNode }) {
return (
// dbMode defaults to 'sync'
{children}
)
}
```
## How It Works
1. Data is stored locally in IndexedDB
2. Changes are immediately available locally (instant UI updates)
3. A WebSocket connection syncs changes to/from the cloud
4. Other devices and users receive updates in real-time
## Benefits
* **Works Offline**: Users can read and write data without an internet connection. Changes sync when they reconnect.
* **Instant UI**: No loading spinners for data operations—changes appear immediately.
* **Real-time Sync**: All connected clients see changes instantly via WebSocket.
* **`useQuery` Auto-refresh**: Components using `useQuery` automatically re-render when data changes.
## Considerations
* Requires IndexedDB (browser environment)
* Initial sync downloads all user data
* Not suitable for server-side rendering of user data
## Example Usage
```tsx theme={null}
'use client'
import { useBasic, useQuery } from '@basictech/nextjs/client'
export function TodoList() {
const { db } = useBasic()
// useQuery subscribes to changes - automatically re-renders
const todos = useQuery(() => db.collection('todos').getAll())
const addTodo = async () => {
// Appears instantly in the UI, syncs to cloud in background
await db.collection('todos').add({ title: 'New todo', completed: false })
}
return (
{todos?.map(todo =>
{todo.title}
)}
)
}
```
***
# Remote Mode
Remote mode makes direct REST API calls to the server without local storage.
```tsx app/providers.tsx theme={null}
'use client'
import { BasicProvider } from '@basictech/nextjs/client'
import { schema } from '../basic.config'
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
## How It Works
1. Every database operation makes an HTTP request to the Basic API
2. No local caching or storage
3. Data is fetched fresh on each request
## Benefits
* **No IndexedDB Dependencies**: Works in environments where IndexedDB isn't available
* **SSR-Friendly**: Better for server-side rendering patterns
* **Simple Architecture**: No sync complexity, just API calls
* **Always Fresh**: Data is always fetched from the server
## Considerations
* **Requires Internet**: No offline support
* **Requires Authentication**: All operations (including reads) require the user to be signed in
* **No Real-time Updates**: `useQuery` won't auto-update; you need to manually refetch
* **Network Latency**: Each operation waits for the network round-trip
## Example Usage
```tsx theme={null}
'use client'
import { useBasic } from '@basictech/nextjs/client'
import { useState, useEffect } from 'react'
export function TodoList() {
const { db, isSignedIn } = useBasic()
const [todos, setTodos] = useState([])
const [loading, setLoading] = useState(true)
// In remote mode, manually fetch and manage state
useEffect(() => {
if (isSignedIn) {
loadTodos()
}
}, [isSignedIn])
const loadTodos = async () => {
setLoading(true)
const data = await db.collection('todos').getAll()
setTodos(data)
setLoading(false)
}
const addTodo = async () => {
await db.collection('todos').add({ title: 'New todo', completed: false })
// Manually refetch after mutation
await loadTodos()
}
if (!isSignedIn) return
Please sign in
if (loading) return
Loading...
return (
{todos.map(todo =>
{todo.title}
)}
)
}
```
***
# Error Handling in Remote Mode
Remote mode throws `NotAuthenticatedError` when attempting write operations without being signed in:
```tsx theme={null}
import { NotAuthenticatedError } from '@basictech/nextjs/client'
const addTodo = async () => {
try {
await db.collection('todos').add({ title: 'New todo' })
} catch (error) {
if (error instanceof NotAuthenticatedError) {
// Prompt user to sign in
console.log('Please sign in to add todos')
} else {
throw error
}
}
}
```
## Graceful Degradation for Reads
In remote mode, read operations gracefully handle unauthenticated state:
| Operation | Unauthenticated Behavior |
| ---------- | ------------------------------ |
| `getAll()` | Returns empty array `[]` |
| `get(id)` | Returns `null` |
| `filter()` | Returns empty array `[]` |
| `add()` | Throws `NotAuthenticatedError` |
| `put()` | Throws `NotAuthenticatedError` |
| `update()` | Throws `NotAuthenticatedError` |
| `delete()` | Throws `NotAuthenticatedError` |
***
# When to Use Each Mode
## Use Sync Mode When:
* Building a PWA or offline-capable app
* You want real-time collaboration features
* Instant UI feedback is important
* Users work with moderate amounts of data
## Use Remote Mode When:
* Building an SSR-heavy Next.js application
* IndexedDB isn't available in your environment
* You don't need offline support
* You want simple, stateless API calls
* Data is too large to sync locally
***
# Checking Current Mode
You can check which mode is active using `dbMode` from `useBasic`:
```tsx theme={null}
'use client'
import { useBasic } from '@basictech/nextjs/client'
export function DbStatus() {
const { dbMode, dbStatus } = useBasic()
return (
Mode: {dbMode}
Status: {dbStatus}
)
}
```
# ⚡️ Quickstart existing React app
Source: https://docs.basic.tech/basic-react/basic-react-sdk
Using the @basictech/react library with your React project
In this guide, you will:
1. Install the Basic React SDK into an existing React project
2. Connect to a Basic project and customize the schema
3. Add sign in and sign out functionality
This library is for client-side React. For Next.js applications, see the [@basictech/nextjs documentation](/basic-nextjs/basic-nextjs-sdk).
# Installation
`cd project-name` into your project, run `npm i` and install the `@basictech/react` library in your app:
```bash Terminal theme={null}
# first, cd into your project
cd project-name
# then, install all packages
npm i
# finally, install Basic
npm install @basictech/react
```
# Set up basic.config
Create a new file called `basic.config.ts` in the root of your project.
Add your project ID to the `basic.config.ts` file, and customize the schema in the file. You can copy your schema from the [admin console](https://admin.basic.tech) (see [Admin & project setup](/get-started/adminportal#create-your-schema)) and paste it into the file.
Any changes you make to the schema in `basic.config` should eventually match the schema published for your project in the admin console. Local edits are for development until you deploy or sync the canonical schema.
```ts basic.config.ts theme={null}
export const schema = {
project_id: 'YOUR_PROJECT_ID',
version: 0,
tables: {
table_name: {
type: 'collection',
fields: {
field_name: {
type: 'string',
indexed: true,
}
}
}
}
}
```
# Adding Auth
Navigate to `src/main.tsx`, add the BasicProvider to imports, and wrap your app in it.
Replace `YOUR_PROJECT_ID` with your project ID from [admin.basic.tech](https://admin.basic.tech). New to Basic? Follow [Admin & project setup](/get-started/adminportal).
```tsx main.tsx theme={null}
// add the BasicProvider to your existing imports
import { BasicProvider } from '@basictech/react'
import { schema } from '../basic.config'
createRoot(document.getElementById('root')).render(
{/* Wrap your app in BasicProvider with your schema */}
,
)
```
Now navigate to `src/App.tsx` and import `signIn`, `isSignedIn`, and `user`. Vite comes with some template code, but you can replace the contents with the following:
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
import './App.css'
function App() {
// Import signIn to enable the user to sign in
// Import isSignedIn to check if the user is signed in
// Import user to get the user's information once they're signed in
const { signIn, isSignedIn, user } = useBasic()
return (
<>
{/* if the user isn't signed in, show the sign in button */}
{!isSignedIn ? (
) : (
// once signed in, show their email
Signed in as: {user?.email}
)}
>
)
}
export default App
```
Let's test it out! Clicking on the button should now redirect you to the [Basic.id](http://Basic.id) page where your users can login or create an account.
Once they're done, they'll be redirected back to your app and see their email displayed.
Use the isSignedIn hook to check if the user is logged in, and the signOut function to sign out the user:
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
import './App.css'
function App() {
// Add signOut to the imports
const { signIn, isSignedIn, user, signOut } = useBasic()
return (
<>
{isSignedIn ? (
Signed in as: {user?.email}
{/* Add a button to sign out */}
) : (
)}
>
)
}
export default App
```
That's it! You've successfully added Basic Auth to your React app 🎉
***
# Using the database
You can use the React SDK to read and write to the user's database. Checkout the [React SDK docs](/sdk-reference/react-components) to get started.
# Set up your Basic account
Source: https://docs.basic.tech/basic-react/react-adminportal
Using the admin portal to create a new project.
# Create your project
Head to [https://app.basic.tech](https://app.basic.tech) to begin creating your project.
Login with your Basic account. If you don't have one yet, signup to Basic.
After creating your Basic account, head to the Dashboard and create a new project.
Use the form to create a project with a unique name and slug. Make sure not to leave the name or slug fields blank.
This page allows you to view all your projects and manage them. To view project details, click into any of them.
You'll need these if you're using the API to connect to your database. Store them securely.
Keep your API Key a secret! Only use it on a secure server environment.
Make sure to replace `` and ``.
```js GET Project Details theme={null}
fetch("https://api.basic.tech/project/", {
headers: {
"Authorization": "Bearer ",
},
}).then(response => response.json())
.then(data => console.log(data));
```
# Create your schema
There are two ways to create your schema, either using the table UI, or by using the code editor. Let's explore the table UI first.
Give your table a unique name, and add fields to it. Fields need to have a unique name within each table, and you can use the dropdown to select the type of each field.
Fields can take on the following types: `string`, `number`, `boolean`, `json`.
You can also edit your schema with the code editor.
Once you've added your tables, you can publish your changes. Upon publishing, you'll notice your version number has been automatically incremented.
You may also discard changes if you'd like to revert to the existing version.
# Next steps
You can now start building your app!
If you are using a React app, we recommend you use the [React SDK](/basic-react/basic-react-sdk) to get started.
Otherwise, you can use the [API](/basic-restapi/basic-api) for any language.
# Schema
Source: https://docs.basic.tech/basic-react/react-schema
Deeper dive into how schemas work in Basic
# Overview
In Basic, we declare schemas in a JSON format as part of the `basic.config.ts` or `basic.config.js` file that lives in your repository.
These `basic.config` files can be committed to git, and unlock "schema-as-code" capabilities for your project.
Basic schemas are very similar to SQL/Postgres tables with the following differences:
* no separate users table required (Basic's datastore per user model already ties data to each specific user)
* no need to declare RLS
* `id` and `created_at` fields are created automatically for you
Schemas would follow this general format:
The top level requires three parameters:
### project\_id
This is your project ID, which you can find in the Basic dashboard, or will be auto-generated for you if you had used the [CLI to create your project](/get-started/cli).
### version
This will be used for versioning your schema. It should be an integer that starts at 0 and increments by 1 for each new version.
# Tables
Let's double click into the tables to learn how they are declared in the schema.
Each table is declared as an object where the key is the table name and the value is an object with the following two parameters:
### type
This refers to the type of database table you are creating. At the moment, Basic only supports `collection`, which will operate similarly to MongoDB collections.
### fields
Fields are the columns of the table. They will be declared as an object of objects. Learn more about the fields below.
# Fields
Each field is declared as an object where the key is the field name and the value is an object with the following 2 parameters:
Default values for fields are not supported yet. Nesting fields is not supported; please use a flat structure for field declarations.
### type
This is the data type of the field. Basic supports the following field types: `string`, `number`, `boolean`, `json`.
### indexed
This lets you control if the field should be indexed in the user's client database for local-first benefits. For now, this should always be set to `true`.
# Example
When put all together, your schema could look like this:
```javascript basic.config.ts theme={null}
export const schema = {
project_id: '1234567890',
version: 0,
tables: {
comments: {
type: 'collection',
fields: {
title: {
type: 'string',
indexed: true
},
body: {
type: 'string',
indexed: true
},
rating: {
type: 'number',
indexed: true
}
}
},
items: {
type: 'collection',
fields: {
name: {
type: 'string',
indexed: true
},
quantity: {
type: 'number',
indexed: true
},
available: {
type: 'boolean',
indexed: true
}
}
}
}
}
```
# Full Guide: Build a to-do app
Source: https://docs.basic.tech/basic-react/to-do-app
A step-by-step guide to building a simple to-do app using Basic
In this guide, you will:
1. Set up a new React project with Basic
2. Create a simple To-Do UI
3. Connect to a Basic project and set up your schema
4. Add sign in and sign out functionality
5. Hook up to Basic DB so that your users can save their to-dos across their devices
## Create a new React project
Use Node.js 24 LTS for this guide. The Basic React SDK requires Node.js 24 or newer.
```bash Terminal theme={null}
npm create vite@latest my-todo-app -- --template react-ts
cd my-todo-app
npm install
```
```bash Terminal theme={null}
npm install @basictech/react
npm install -D tailwindcss@^4 @tailwindcss/vite@^4
```
Add Tailwind's Vite plugin alongside the React plugin in `vite.config.ts`:
```ts vite.config.ts theme={null}
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
```
Replace `src/index.css` with:
```css src/index.css theme={null}
@import "tailwindcss";
```
Tailwind 4 detects your source files automatically. You do not need a
`tailwind.config.js`, PostCSS configuration, or an `init` command for this setup.
```bash Terminal theme={null}
npm run dev
```
Navigate to `http://localhost:5173` to see your app running.
## Create a simple To-Do UI
Replace the code in your `src/App.tsx` file:
```tsx src/App.tsx theme={null}
import './App.css'
function App() {
return (
my lofi to-do app
)
}
export default App
```
## Connect to a Basic project
Go to [admin.basic.tech](https://admin.basic.tech) and create a free account. Then create a new project and copy your Project ID.
Create a `basic.config.ts` file in your project root:
```ts basic.config.ts theme={null}
export const schema = {
project_id: "YOUR_PROJECT_ID", // Replace with your project ID
version: 0,
tables: {
todos: {
type: "collection",
fields: {
name: {
type: "string"
},
completed: {
type: "boolean"
}
}
}
}
}
```
Update `src/main.tsx`:
```tsx src/main.tsx theme={null}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BasicProvider } from '@basictech/react'
import { schema } from '../basic.config'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
,
)
```
## Add sign in and sign out functionality
Update `src/App.tsx`:
```tsx src/App.tsx theme={null}
import { useBasic } from '@basictech/react'
import './App.css'
function App() {
const { signIn, signOut, isSignedIn, user } = useBasic()
return (
my lofi to-do app
{isSignedIn ? (
Signed in as: {user?.email}
) : (
)}
)
}
export default App
```
## Hook up to Basic DB
Update `src/App.tsx` to add state and the `addTask` function:
```tsx src/App.tsx theme={null}
import { useBasic } from '@basictech/react'
import { useState } from 'react'
import './App.css'
function App() {
const { signIn, signOut, isSignedIn, user, db } = useBasic()
const [taskInput, setTaskInput] = useState('')
const addTask = async () => {
if (!taskInput.trim()) return
await db.collection('todos').add({
name: taskInput,
completed: false
})
setTaskInput('')
}
return (
)
}
export default App
```
And that's it! You've built a local-first to-do app that allows your users to sign in and save their to-dos across their devices.
Notice how snappily all the changes are reflected - from task creation to deletion. This is the power of local-first architecture!
## Next steps
* [Database Methods](/sdk-reference/react-db) - Learn about all available database operations
* [React Hooks](/sdk-reference/react-hooks) - Explore all available hooks
* [Schema Guide](/basic-react/react-schema) - Learn how to customize your schema
# Set up your Basic account
Source: https://docs.basic.tech/basic-restapi/api-adminportal
Using the admin portal to create a new project.
# Create your project
Head to [https://app.basic.tech](https://app.basic.tech) to begin creating your project.
Login with your Basic account. If you don't have one yet, signup to Basic.
After creating your Basic account, head to the Dashboard and create a new project.
Use the form to create a project with a unique name and slug. Make sure not to leave the name or slug fields blank.
This page allows you to view all your projects and manage them. To view project details, click into any of them.
You'll need these if you're using the API to connect to your database. Store them securely.
Keep your API Key a secret! Only use it on a secure server environment.
Make sure to replace `` and ``.
```js GET Project Details theme={null}
fetch("https://api.basic.tech/project/", {
headers: {
"Authorization": "Bearer ",
},
}).then(response => response.json())
.then(data => console.log(data));
```
# Create your schema
There are two ways to create your schema, either using the table UI, or by using the code editor. Let's explore the table UI first.
Give your table a unique name, and add fields to it. Fields need to have a unique name within each table, and you can use the dropdown to select the type of each field.
Fields can take on the following types: `string`, `number`, `boolean`, `json`.
You can also edit your schema with the code editor.
Once you've added your tables, you can publish your changes. Upon publishing, you'll notice your version number has been automatically incremented.
You may also discard changes if you'd like to revert to the existing version.
# Next steps
You can now start building your app!
If you are using a React app, we recommend you use the [React SDK](/basic-react/basic-react-sdk) to get started.
Otherwise, you can use the [API](/basic-restapi/basic-api) for any language.
# Filtering, ordering, pagination
Source: https://docs.basic.tech/basic-restapi/api-filtering
Learn how to filter results using the APIs
## Querying
Chain after `GET https://api.basic.tech/account/{project_id}/db/{table}` endpoint as query params.
### Filtering (`filter`)
```js theme={null}
const options = {method: 'GET'};
//example to filter where name equals John
fetch('https://api.basic.tech/account/{project_id}/db/{table}?name=eq.John', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
* **Method:** `filter(conditions)`
* **Supported Operators:** eq, neq, gt, gte, lt, lte, like, ilike, in, not (see [list of operators](#supported-operators))
### Ordering (`order`)
```js theme={null}
const options = {method: 'GET'};
//example to filter where created_at is descending, and name is ascending
fetch('https://api.basic.tech/account/{project_id}/db/{table}?order=created_at.desc,name.asc', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
* **Method:** `order(field, direction?)` (`direction` defaults to 'asc')
### Pagination (`limit`, `offset`)
```js theme={null}
const options = {method: 'GET'};
//example to filter where limit is 10, offset is 20
fetch('https://api.basic.tech/account/{project_id}/db/{table}?limit=10&offset=20', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
* **Methods:** `limit(count)`, `offset(count)`
## Supported operators
| Operator | Description | Example |
| -------- | -------------------------- | ------------------------ |
| `eq` | Equality | `status=eq.active` |
| `neq` | Not equal | `status=neq.deleted` |
| `gt` | Greater than | `priority=gt.3` |
| `gte` | Greater than or equal | `priority=gte.3` |
| `lt` | Less than | `priority=lt.3` |
| `lte` | Less than or equal | `priority=lte.3` |
| `like` | Pattern (case sensitive) | `title=like.*project*` |
| `ilike` | Pattern (case insensitive) | `title=ilike.*todo*` |
| `in` | Value in set | `tags=in.work,important` |
| `not` | Negation | `tags=not.eq.work` |
## Limitations
* Only one filterable condition per field per query (no compound filters across multiple fields)
* Range filters (e.g., `?field=gte.x<e.y`) not supported
* Method chaining order is flexible, but all must be chained after `GET https://api.basic.tech/account/{project_id}/db/{table}`
# Schema
Source: https://docs.basic.tech/basic-restapi/api-schema
Deeper dive into how schemas work in Basic
# Overview
In Basic, we declare schemas in a JSON format as part of the `basic.config.ts` or `basic.config.js` file that lives in your repository.
These `basic.config` files can be committed to git, and unlock "schema-as-code" capabilities for your project.
Basic schemas are very similar to SQL/Postgres tables with the following differences:
* no separate users table required (Basic's datastore per user model already ties data to each specific user)
* no need to declare RLS
* `id` and `created_at` fields are created automatically for you
Schemas would follow this general format:
The top level requires three parameters:
### project\_id
This is your project ID, which you can find in the Basic dashboard, or will be auto-generated for you if you had used the [CLI to create your project](/get-started/cli).
### version
This will be used for versioning your schema. It should be an integer that starts at 0 and increments by 1 for each new version.
# Tables
Let's double click into the tables to learn how they are declared in the schema.
Each table is declared as an object where the key is the table name and the value is an object with the following two parameters:
### type
This refers to the type of database table you are creating. At the moment, Basic only supports `collection`, which will operate similarly to MongoDB collections.
### fields
Fields are the columns of the table. They will be declared as an object of objects. Learn more about the fields below.
# Fields
Each field is declared as an object where the key is the field name and the value is an object with the following 2 parameters:
Default values for fields are not supported yet. Nesting fields is not supported; please use a flat structure for field declarations.
### type
This is the data type of the field. Basic supports the following field types: `string`, `number`, `boolean`, `json`.
### indexed
This lets you control if the field should be indexed in the user's client database for local-first benefits. For now, this should always be set to `true`.
# Example
When put all together, your schema could look like this:
```javascript basic.config.ts theme={null}
export const schema = {
project_id: '1234567890',
version: 0,
tables: {
comments: {
type: 'collection',
fields: {
title: {
type: 'string',
indexed: true
},
body: {
type: 'string',
indexed: true
},
rating: {
type: 'number',
indexed: true
}
}
},
items: {
type: 'collection',
fields: {
name: {
type: 'string',
indexed: true
},
quantity: {
type: 'number',
indexed: true
},
available: {
type: 'boolean',
indexed: true
}
}
}
}
}
```
# ⚡️ Quickstart
Source: https://docs.basic.tech/basic-restapi/basic-api
Integrate Basic into your project using REST APIs
First, check if there's an existing Basic SDK for your technology stack and use it instead for a better dev experience!
# Summary
There are two main steps to integrating Basic into your app: implement Basic Auth, then call the database APIs with the user's access token.
Implement signup / login flows for your app's users
Write and display data safely in your app
# Basic Auth APIs guide
Basic uses OAuth 2.0 for authentication. For request details, see the [sign-in API reference](/api-reference/auth/redirect-to-sign-in).
In this guide, you will:
1. Use "redirect to sign in" endpoint to redirect users to account creation / login flow
2. Extract code from URL
3. Use "get auth token" endpoint with extracted code
4. Store auth token object for future API calls
Create a button that redirects the user to the Basic Auth page, with the following required parameters (more info in the [sign-in API reference](/api-reference/auth/redirect-to-sign-in)):
* `client_id='YOUR_CLIENT_ID'`
* `redirect_uri='YOUR_REDIRECT_URI'`
* `response_type=code`
* `scope=profile`
* `state='YOUR_STATE'`
Redirect URL template: `https://api.basic.tech/auth/authorize?response_type=code&redirect_uri=YOUR_REDIRECT_URI&scope=profile&state=YOUR_STATE&client_id=YOUR_CLIENT_ID`
Replace `YOUR_CLIENT_ID`, `YOUR_REDIRECT_URI`, and `YOUR_STATE` with your own values. `scope` must be a space-separated list (for example `profile` alone, or `profile` plus datastore scopes your app needs). The live contract matches the [OpenAPI spec](/api-reference/auth/redirect-to-sign-in) (`GET /auth/authorize`).
**PKCE (recommended):** You may include `code_challenge` and `code_challenge_method=S256` on the authorize request and the matching `code_verifier` when exchanging the code at `/auth/token`. See the [sign-in](/api-reference/auth/redirect-to-sign-in) and [token](/api-reference/auth/get-auth-token) API references for request details.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.basic.tech/auth/authorize?response_type=code&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'&client_id='YOUR_CLIENT_ID''
```
```python Python theme={null}
import requests
url = "https://api.basic.tech/auth/authorize"
querystring = {"response_type":"code","client_id":"YOUR_CLIENT_ID","redirect_uri":"YOUR_REDIRECT_URI","scope":"profile","state":"YOUR_STATE"}
response = requests.request("GET", url, params=querystring)
print(response.text)
```
```javascript JavaScript theme={null}
const options = {method: 'GET'};
fetch('https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```php PHP theme={null}
"https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
```
```java Java theme={null}
HttpResponse response = Unirest.get("https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'")
.asString();
```
The authorization code will be in the URL as a `code` query parameter.
For example:
`https://your-app.com/callback?code=1234567890`.
You need to extract the code from the URL and use it in the next step.
```bash cURL theme={null}
# Assuming the URL is stored in a variable
URL="https://your-app.com/callback?code=1234567890"
CODE=$(echo $URL | grep -o 'code=[^&]*' | cut -d'=' -f2)
echo $CODE
```
```python Python theme={null}
from urllib.parse import urlparse, parse_qs
# Example URL
url = "https://your-app.com/callback?code=1234567890"
# Parse the URL and extract the code parameter
parsed_url = urlparse(url)
code = parse_qs(parsed_url.query)['code'][0]
print(code)
```
```javascript JavaScript theme={null}
// Example URL
const url = "https://your-app.com/callback?code=1234567890";
// Extract the code parameter
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
console.log(code);
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"net/url"
)
func main() {
// Example URL
rawURL := "https://your-app.com/callback?code=1234567890"
// Parse the URL and extract the code parameter
parsedURL, _ := url.Parse(rawURL)
code := parsedURL.Query().Get("code")
fmt.Println(code)
}
```
```java Java theme={null}
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// Example URL
String url = "https://your-app.com/callback?code=1234567890";
// Parse the URL and extract the code parameter
String query = URI.create(url).getQuery();
Map params = new HashMap<>();
for (String param : query.split("&")) {
String[] pair = param.split("=");
params.put(pair[0], URLDecoder.decode(pair[1], StandardCharsets.UTF_8));
}
String code = params.get("code");
System.out.println(code);
}
}
```
Make a post request to `/auth/token` with the authorization code in the body. The response will be a JSON object with the access token and a refresh code.
Token URL template:
`https://api.basic.tech/auth/token`
Make sure to replace `YOUR_CODE` with your own values
```bash cURL theme={null}
curl --request POST \
--url 'https://api.basic.tech/auth/token' \
--header 'Content-Type: application/json' \
--data '{"code":"YOUR_CODE"}'
```
```python Python theme={null}
import requests
url = "https://api.basic.tech/auth/token"
payload = {"code": "YOUR_CODE"}
headers = {"Content-Type": "application/json"}
response = requests.request("POST", url, json=payload, headers=headers)
print(response.text)
```
```javascript JavaScript theme={null}
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: 'YOUR_CODE'})
};
fetch('https://api.basic.tech/auth/token', options)
.then(response => response.json())
.then(response => console.log(response))
```
```php PHP theme={null}
"https://api.basic.tech/auth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "{\n \"code\": \"YOUR_CODE\"\n}",
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
```
```go Go theme={null}
package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "https://api.basic.tech/auth/token"
payload := strings.NewReader("{\n \"code\": \"YOUR_CODE\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}
```
```java Java theme={null}
HttpResponse response = Unirest.post("https://api.basic.tech/auth/token")
.header("Content-Type", "application/json")
.body("{\n \"code\": \"YOUR_CODE\"\n}")
.asString();
```
The response from the token endpoint will look like this:
```json theme={null}
{
"access_token": "",
"token_type": "",
"expires_in": 123,
"refresh_token": ""
}
```
# Basic database APIs guide
The [database APIs](/api-reference/project-users/create-item) are easy to use.
Make sure to check the validity of the `access_token`, and pass it in the Header of your API call.
If the `access_token` is expired, you'd repeat step 3 of the [Auth APIs guide](/basic-restapi/basic-api#basic-auth-apis-guide) but using the `refresh_token` instead of the extracted code. This will return you a fresh new `access_token`.
Here's how to validate and use the access token in different programming languages:
```bash cURL theme={null}
# Note: For bash, you'll need to use a JWT decoding tool like jq or jwt-cli
# Example using jwt-cli:
# if jwt decode YOUR_ACCESS_TOKEN | jq '.exp > now' | grep -q "true"; then
# # Token is valid, proceed with API calls
curl --request GET \
--url 'https://api.basic.tech/your-endpoint' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
# else
# # Token is expired, refresh it
curl --request POST \
--url 'https://api.basic.tech/auth/token' \
--header 'Content-Type: application/json' \
--data '{"refresh_token":"YOUR_REFRESH_TOKEN"}'
```
```python Python theme={null}
import jwt
import time
import requests
def refresh_token(refresh_token):
response = requests.post(
'https://api.basic.tech/auth/token',
json={'refresh_token': refresh_token},
headers={'Content-Type': 'application/json'}
)
return response.json()
def is_token_valid(access_token):
try:
# Decode the token without verification
decoded = jwt.decode(access_token, options={"verify_signature": False})
# Check if token is expired
return decoded['exp'] > time.time()
except:
return False
# Example usage
if is_token_valid(access_token):
# Token is valid, proceed with API calls
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get('https://api.basic.tech/your-endpoint', headers=headers)
else:
# Token is invalid, refresh it
new_tokens = refresh_token(refresh_token)
access_token = new_tokens['access_token']
refresh_token = new_tokens['refresh_token']
# Now use the new access token
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get('https://api.basic.tech/your-endpoint', headers=headers)
```
```javascript JavaScript theme={null}
import jwtDecode from 'jwt-decode';
async function refreshToken(refreshToken) {
const response = await fetch('https://api.basic.tech/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refresh_token: refreshToken })
});
return await response.json();
}
function isTokenValid(accessToken) {
try {
const decoded = jwtDecode(accessToken);
// Check if token is expired
return decoded.exp * 1000 > Date.now();
} catch (error) {
return false;
}
}
// Example usage
if (isTokenValid(accessToken)) {
// Token is valid, proceed with API calls
const response = await fetch('https://api.basic.tech/your-endpoint', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
} else {
// Token is invalid, refresh it
const newTokens = await refreshToken(refreshToken);
accessToken = newTokens.access_token;
refreshToken = newTokens.refresh_token;
// Now use the new access token
const response = await fetch('https://api.basic.tech/your-endpoint', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
}
```
```php PHP theme={null}
$refreshToken
]));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
function isTokenValid($accessToken) {
try {
$decoded = JWT::decode($accessToken, null, false);
return $decoded->exp > time();
} catch (Exception $e) {
return false;
}
}
// Example usage
if (isTokenValid($accessToken)) {
// Token is valid, proceed with API calls
$ch = curl_init('https://api.basic.tech/your-endpoint');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken
]);
$response = curl_exec($ch);
curl_close($ch);
} else {
// Token is invalid, refresh it
$newTokens = refreshToken($refreshToken);
$accessToken = $newTokens['access_token'];
$refreshToken = $newTokens['refresh_token'];
// Now use the new access token
$ch = curl_init('https://api.basic.tech/your-endpoint');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken
]);
$response = curl_exec($ch);
curl_close($ch);
}
?>
```
```go Go theme={null}
package main
import (
"fmt"
"time"
"net/http"
"io/ioutil"
"encoding/json"
"github.com/golang-jwt/jwt/v5"
)
type TokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func refreshToken(refreshToken string) (*TokenResponse, error) {
payload := map[string]string{
"refresh_token": refreshToken,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.basic.tech/auth/token", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tokenResp TokenResponse
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
return &tokenResp, err
}
func isTokenValid(accessToken string) bool {
token, _, err := jwt.NewParser().ParseUnverified(accessToken, jwt.MapClaims{})
if err != nil {
return false
}
if claims, ok := token.Claims.(jwt.MapClaims); ok {
exp := claims["exp"].(float64)
return int64(exp) > time.Now().Unix()
}
return false
}
func main() {
if isTokenValid(accessToken) {
// Token is valid, proceed with API calls
req, _ := http.NewRequest("GET", "https://api.basic.tech/your-endpoint", nil)
req.Header.Add("Authorization", "Bearer "+accessToken)
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
} else {
// Token is invalid, refresh it
newTokens, err := refreshToken(refreshToken)
if err != nil {
fmt.Println("Error refreshing token:", err)
return
}
accessToken = newTokens.AccessToken
refreshToken = newTokens.RefreshToken
// Now use the new access token
req, _ := http.NewRequest("GET", "https://api.basic.tech/your-endpoint", nil)
req.Header.Add("Authorization", "Bearer "+accessToken)
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
}
```
```java Java theme={null}
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.util.Date;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import com.google.gson.Gson;
class TokenResponse {
String access_token;
String refresh_token;
}
public class TokenValidator {
private static final HttpClient client = HttpClient.newHttpClient();
private static final Gson gson = new Gson();
public static TokenResponse refreshToken(String refreshToken) throws Exception {
String json = String.format("{\"refresh_token\":\"%s\"}", refreshToken);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.basic.tech/auth/token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), TokenResponse.class);
}
public static boolean isTokenValid(String accessToken) {
try {
Claims claims = Jwts.parserBuilder()
.build()
.parseClaimsJwt(accessToken)
.getBody();
Date expiration = claims.getExpiration();
return expiration.after(new Date());
} catch (Exception e) {
return false;
}
}
public static void main(String[] args) {
if (isTokenValid(accessToken)) {
// Token is valid, proceed with API calls
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.basic.tech/your-endpoint"))
.header("Authorization", "Bearer " + accessToken)
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Token is invalid, refresh it
try {
TokenResponse newTokens = refreshToken(refreshToken);
accessToken = newTokens.access_token;
refreshToken = newTokens.refresh_token;
// Now use the new access token
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.basic.tech/your-endpoint"))
.header("Authorization", "Bearer " + accessToken)
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
```
# Set up your Basic account
Source: https://docs.basic.tech/get-started/adminportal
Using the admin portal to create a new project.
# Create your project
Head to [https://app.basic.tech](https://app.basic.tech) to begin creating your project.
Login with your Basic account. If you don't have one yet, signup to Basic.
After creating your Basic account, head to the Dashboard and create a new project.
Use the form to create a project with a unique name and slug. Make sure not to leave the name or slug fields blank.
This page allows you to view all your projects and manage them. To view project details, click into any of them.
You'll need these if you're using the API to connect to your database. Store them securely.
Keep your API Key a secret! Only use it on a secure server environment.
Make sure to replace `` and ``.
```js GET Project Details theme={null}
fetch("https://api.basic.tech/project/", {
headers: {
"Authorization": "Bearer ",
},
}).then(response => response.json())
.then(data => console.log(data));
```
# Create your schema
There are two ways to create your schema, either using the table UI, or by using the code editor. Let's explore the table UI first.
Give your table a unique name, and add fields to it. Fields need to have a unique name within each table, and you can use the dropdown to select the type of each field.
Fields can take on the following types: `string`, `number`, `boolean`, `json`.
You can also edit your schema with the code editor.
Once you've added your tables, you can publish your changes. Upon publishing, you'll notice your version number has been automatically incremented.
You may also discard changes if you'd like to revert to the existing version.
# Next steps
You can now start building your app!
If you are using a React app, we recommend you use the [React SDK](/basic-react/basic-react-sdk) to get started.
Otherwise, you can use the [API](/basic-restapi/basic-api) for any language.
# Set up the Basic CLI (optional)
Source: https://docs.basic.tech/get-started/cli
Using the CLI or code editor LLMs to manage projects.
## Install the CLI
First, install the CLI using your terminal.
```bash theme={null}
npm i -g @basictech/cli
```
Run `basic login` to create your basic admin account. You will be redirected to a web page to login.
```bash theme={null}
basic login
```
# Create / integrate a project
Navigate to your project directory and run `basic init` to create your project.
```bash theme={null}
cd project-name
```
```bash theme={null}
basic init
```
You will be prompted to choose between creating a new project or integrating an existing project.
Choose your tech stack and project name.
That's it! You now have a new project created. You'll even notice a new `basic.config.ts` / `basic.config.js` file in your project directory with your project ID included!
If you run `basic init` in a codebase with an existing `basic.config.ts` file (e.g., created using via [create-lofi-app](/basic-react/create-lofi-app)), you'll notice that the project\_id in your `basic.config.ts` file gets automatically updated.
# Other CLI commands
**View all commands**
```bash theme={null}
basic help
```
Other commands include:
* `basic account` - Show account information
* `basic logout` - logout from your basic account
* `basic status` - Show login status
* `basic projects` - list your projects
* `basic version` - Show CLI version
* `basic update` - Update CLI to the latest version
* `basic debug` - Show Basic config directory location
# What is Basic?
Source: https://docs.basic.tech/get-started/welcome
Build personalized apps and agents with Basic.
the web in your hands
## Features and benefits
Basic is a purpose-built backend for personalization of apps and agents. Instead of one shared database for all users, Basic uses a **federated model**: a dedicated datastore per user in the cloud. You read and write through SDKs or APIs with built-in auth and permissioning.
* **Zero infrastructure**: No database to provision for that layer—CRUD via SDKs and HTTP.
* **Shared user context across apps**: When users connect multiple apps to Basic, you can work with the same permissioned datastore model (see [The PDS](/readings/personal-data-stores)).
* **Built-in auth and permissioning**: Sign-in and app approval let users choose which apps can access their data (see [Auth](/readings/auth-basic)).
* **Federated, per-user datastores**: Portable, user-centric data for deep personalization.
* **Responsive clients**: Local-first sync where you use the web/mobile SDKs; near-instant UI with offline-friendly patterns.
*On the roadmap: broader real-time collaboration patterns, federated learning, and more.*
## Use cases
* **AI apps and agents**: Store and retrieve user context across sessions safely.
* **AI app builders**: Hand thousands of end-user apps a consistent auth + data layer.
* **AI wearables and sensitive UX**: Keep user data scoped to their store without leaking it across tenants.
* **Embedded AI**: Personalize models using permissioned user context, not only generic defaults.
* **Personal and productivity software**: Sync and offline-friendly UX for competitive polish.
## Getting started
First, [set up your Basic account and project](/get-started/adminportal). Optionally [install the Basic CLI](/get-started/cli).
Then pick docs for your stack from the dropdown in the **Build** tab:
| Framework / API | Support |
| ------------------- | :-----: |
| React | Yes |
| Next.js | Yes |
| Expo / React Native | Yes |
| REST | Yes |
| Management APIs | Yes |
*More client runtimes are on the [roadmap](/readings/others/roadmap).*
**Implementation guides:**
Integrate Basic into your React project
Integrate Basic into your Next.js project
Integrate Basic into your Expo / React Native project
Integrate Basic over HTTP
## Side quests
Core concepts behind user-owned data
Feedback or help getting started
Product updates
# Auth
Source: https://docs.basic.tech/readings/auth-basic
One account. You choose which apps to connect.
If your data has a home of its own, apps need a way to know it's yours—and whether you've invited them in. That's what auth is for.
## Signing in and connecting an app
Your Basic account is your identity across apps built with Basic. You can use the same account rather than starting from scratch in each app.
There are two separate choices:
* **Signing in** confirms which account is yours.
* **Approving an app** lets it work with the data it's allowed to use.
Signing in alone doesn't open your whole PDS to an app. A notes app can work with your notes without getting access to unrelated information.
## You can change your mind
You can disconnect an app when you no longer want it to have access. The data it saved in your PDS stays there.
This separates your account from any one app. You aren't handing over your data's home every time you try a new tool—you're choosing who can use it.
# Sync
Source: https://docs.basic.tech/readings/local-first-sync
Work on your device. Keep your data together.
An app should feel quick when you use it, and your work should be there when you switch devices. Sync connects those two experiences.
## Work here, catch up everywhere
Apps using Basic's sync keep a working copy of your data on your device. Changes are saved there first, so you don't have to wait for the internet after every action. This is called **local-first**.
When connected, the app sends changes to your PDS and receives updates made elsewhere. Your PDS keeps the main version that your devices catch up with.
For example, you can edit a downloaded note while offline. When you reconnect and it syncs, that edit becomes available on your other devices. Until then, it only exists on the device where you made it.
## The internet can wait—sometimes
Offline work depends on what an app keeps on your device. Not every feature works offline, and working with someone else's shared data may still need a connection.
If two edits can't be combined safely, the app may need to help resolve them. Sync keeps changes moving between devices; it can't always decide what you meant.
# Multiplayer & sharing
Source: https://docs.basic.tech/readings/multiplayer-and-sharing
Work together without giving up ownership.
Personal data doesn't have to mean solitary data. Some things are more useful together: a shopping list, a trip plan, or a collection of ideas.
## An invitation to the same data
Sharing lets you invite another person to selected data. They accept the invitation and can view it, or help edit it if you've allowed that.
The data stays in the owner's PDS. The other person gets access to it—not a separate copy in their own PDS, and not access to everything you own.
## Working together
When people can see each other's changes and work on the same information, we call that **multiplayer**.
Imagine a shared shopping list. One person adds bread; another checks off milk. Both are working on the same list, rather than sending updated copies back and forth.
The owner can end access later. That stops further access through Basic, but it can't take back information someone has already downloaded or copied.
Sharing should make your data more useful without making you give up control of its home.
# External resources
Source: https://docs.basic.tech/readings/others/reading
Background reading on local-first and user-owned data
Curated links for context on **local-first software**, **user-owned data**, and adjacent ecosystems. Basic does not endorse every project below; they are useful for comparison and learning.
## Local-first and sync
* [Ink & Switch — local-first](https://www.inkandswitch.com/local-first/) — foundational essay on the pattern.
* [localfirstweb.dev](https://localfirstweb.dev/) — community and resources.
* [Cambria (Ink & Switch)](https://www.inkandswitch.com/cambria) — schema evolution and JSON interoperability.
## User-owned and decentralized identity
* [Solid](https://solidproject.org/) — pods and linked data.
* [Autonomous data](https://autonomous-data.noeldemartin.com/introduction.html) — framing for user-held data.
* [unhosted.org](https://unhosted.org/) — “no backend” / client-side apps with remote storage.
* [0data.app](https://0data.app) — catalog of zero-knowledge / user-owned patterns.
* [Fission](http://fission.codes), [remoteStorage](http://remotestorage.io), [Matrix](http://matrix.org) — related stacks and protocols.
## Tools in the local-first space
* [Instant](https://www.instantdb.com/), [Electric SQL](https://electric-sql.com/), [PowerSync](https://www.powersync.com/) — sync and realtime databases.
* [Obsidian](https://obsidian.md) — local-first notes (product example).
# Roadmap
Source: https://docs.basic.tech/readings/others/roadmap
High-level product direction for Basic.
Public direction (not commitments or ETAs):
* **SDKs and clients**: Broader first-party support (for example Python, native iOS/Android) beyond React, Next.js, Expo, and REST.
* **Collaboration**: Richer real-time and multiplayer-style patterns on top of sync.
* **Schema and types**: Stronger TypeScript and codegen from schema definitions.
* **History and undo**: Versioning primitives for user-visible undo/redo style flows.
* **Sync**: Smarter partial sync so large accounts do not need the full corpus on every device.
* **Deployment**: Self-hosted and bring-your-own-infrastructure options for datastores.
For what ships when, follow [Twitter / X](https://twitter.com/basic_db) and [Discord](https://discord.gg/PuWD3zhsCk)—this list is intentionally high level.
# The PDS
Source: https://docs.basic.tech/readings/personal-data-stores
A home for your data, independent of the apps you use.
Most apps keep your data inside their own systems. Your notes live in one app, your plans in another. Switching tools often means leaving something behind or starting over.
Basic starts with a different idea: **your data should belong to you, not to the app you used to create it.**
## A home of your own
A **personal data store**, or **PDS**, is the home for your data in Basic. Apps connect to it with your permission to read and save information.
Inside your PDS, data is organized into separate spaces. Each space belongs to your account, even when an app creates it. Connecting an app doesn't give it access to everything else.
Basic currently hosts your PDS in the cloud. Your devices can keep local copies, while your PDS keeps the shared, up-to-date version as they sync.
## Apps are tools, not containers
Think of a notes app as a way to work with your notes, rather than the place that owns them. Disconnecting the app doesn't delete the notes from your PDS.
Another compatible app can work with that same data if you allow it. The goal is simple: you should be able to choose better tools without leaving your information behind.
# Apps & schemas
Source: https://docs.basic.tech/readings/schema
Different tools can understand the same data.
Your PDS gives data a home. Apps give you ways to use it: write a note, plan a trip, or organize a collection.
For an app to work with data, it needs to understand how that data is arranged. That's what a **schema** describes.
## A description that stays with the data
A note might have a title and some text. A task might have a name, a due date, and a mark saying whether it's done. A schema describes those parts so apps know what to expect.
Basic keeps that description alongside the data in your PDS. It doesn't exist only inside the app that created it.
## More than one way to use your information
Two compatible apps could show the same tasks differently: one as a checklist, another as a calendar. With your permission, both can work with the same underlying information.
This doesn't mean every app automatically understands every kind of data. Apps still need to support its structure and have your approval to use it.
The aim is to let apps offer different experiences without each needing a separate copy of your life.
Ready to make something? Head to [Build](/get-started/welcome).
# Database (get, add, delete, etc.)
Source: https://docs.basic.tech/sdk-reference/expo-rn
Learn how to access data using the Expo / RN SDK
Use these methods on any table. You'd select a table using `db.from(*insert table name*)`, and chain it with any of the following methods:
* `getAll()`
* `get(id)`
* `add(value)`
* `update(id, value)`
* `replace(id, value)`
* `delete(id)`
Make sure not to create any `id` or `created_at` field since these are reserved and exist with every row. You can access them with the `get` operators
### Get all records
```js theme={null}
const allNotes = await db.from('notes').getAll();
```
* **Method:** `getAll()`
* **Returns:** Query builder for chaining (see [filtering and ordering](/basic-expo-rn/rn-filtering) for instructions)
### Get by ID
```js theme={null}
const note = await db.from('notes').get('note-id-here');
```
* **Method:** `get(id)`
* **Returns:** Record object or throws if not found
### Add a record
```js theme={null}
const newNote = await db.from('notes').add({...});
```
* **Method:** `add(value)`
* **Returns:** The newly added record
### Update a record
```js theme={null}
const updated = await db.from('notes').update('noteId', { completed: true })
```
* **Method:** `update(id, value)`
### Replace a record
```js theme={null}
const replaced = await db.from('notes').replace('noteId', {...})
```
* **Method:** `replace(id, value)`
### Delete a record
```js theme={null}
const deleted = await db.from('notes').delete('noteId')
```
* **Method:** `delete(id)`
# Components (BasicProvider)
Source: https://docs.basic.tech/sdk-reference/react-components
# BasicProvider
To enable the features of the Basic SDK, you need to wrap your app in a `` component.
```tsx main.tsx theme={null}
import { BasicProvider } from '@basictech/react'
import schema from './basic.config'
function App() {
return (
)
}
```
## Props
### schema
The Basic schema object containing your project configuration and table definitions. This should be imported from your `basic.config.ts` file.
The `project_id` is extracted from `schema.project_id`.
### project\_id
The project ID of your Basic project.
**Deprecated**: Project ID is now extracted from `schema.project_id`. This prop is kept for backward compatibility but can be omitted if your schema includes the project\_id.
### debug
Enable debug mode for additional logging to the console. Useful during development.
```tsx theme={null}
```
### dbMode
Determines which database implementation is used:
* **`'sync'`** (default): Uses IndexedDB + WebSocket for local-first sync. Works offline, provides real-time updates, and `useQuery` auto-refreshes.
* **`'remote'`**: Uses REST API calls directly to the server. No local storage, requires authentication for all operations, but has no IndexedDB dependencies.
```tsx theme={null}
// Use remote mode for SSR-heavy apps or when you don't need offline support
```
See [Database Modes](/basic-nextjs/nextjs-modes) for detailed comparison.
### storage
Custom storage adapter for persisting auth tokens. By default, uses `LocalStorageAdapter` which stores data in `localStorage`.
Implement the `BasicStorage` interface to use a custom storage solution (e.g., for React Native with AsyncStorage):
```typescript theme={null}
interface BasicStorage {
get(key: string): Promise
set(key: string, value: string): Promise
remove(key: string): Promise
}
```
```tsx theme={null}
import { BasicProvider, BasicStorage } from '@basictech/react'
import AsyncStorage from '@react-native-async-storage/async-storage'
const asyncStorageAdapter: BasicStorage = {
get: (key) => AsyncStorage.getItem(key),
set: (key, value) => AsyncStorage.setItem(key, value),
remove: (key) => AsyncStorage.removeItem(key)
}
```
### auth
Custom authentication configuration. Most apps omit this and use defaults.
```typescript theme={null}
type AuthConfig = {
scopes?: string | string[] // default: 'profile,email,app:admin'
pds_url?: string // PDS / auth host (default: 'https://pds.basic.id')
admin_url?: string // Admin API for telemetry (default: 'https://api.basic.tech')
ws_url?: string // Sync WebSocket (default: 'wss://pds.basic.id/ws')
/** @deprecated */ server_url?: string // use pds_url instead
}
```
```tsx theme={null}
```
### devToolbar
When `true`, shows the lazy-loaded **Basic dev toolbar** (schema status, useful shortcuts) in environments where it is allowed (for example localhost, `NODE_ENV=development`, or when `debug` is enabled on the provider). Export `BasicDevToolbar` from `@basictech/react` if you want to place it yourself.
***
## Full Example
```tsx main.tsx theme={null}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BasicProvider } from '@basictech/react'
import { schema } from './basic.config'
import App from './App'
createRoot(document.getElementById('root')!).render(
)
```
# Database (get, add, update, delete, etc.)
Source: https://docs.basic.tech/sdk-reference/react-db
# Overview
There are 7 methods you can use to interact with the database.
They are used in conjunction with the `db` property from the `useBasic()` hook, and are appended in the format of `db.collection('tablename').METHOD()`.
## Methods
### .get()
Fetch a single item from the table by its ID. Returns `null` if not found.
### .getAll()
Fetch all items from the table. Returns an empty array if no items exist.
In the **React / Next.js client SDK** (sync or remote mode), `.getAll()` loads items for that collection and `.filter()` applies your predicate **on the client** after fetch. For **server-side query parameters** on HTTP, use the [REST API filtering guide](/basic-restapi/api-filtering) against the PDS endpoints instead.
### .add()
Adds a new item to the table. The `id` is automatically generated by the server. Returns the created object with its new ID.
### .put()
Upsert (insert or replace) an item. The `data` object **must include an `id` field**. If an item with that ID exists, it will be replaced entirely. If not, a new item will be created. Returns the upserted object.
### .update()
Partially update an existing item by ID. Only the fields you provide will be updated—other fields remain unchanged. Returns the updated object, or `null` if the item was not found.
### .delete()
Deletes an item from the table by ID. Returns `true` if deleted, `false` if the item was not found.
### .filter()
Filter records using a predicate function. This fetches all records and filters client-side. Returns an array of matching objects.
***
# Examples
### Read items
There are 2 ways to read items from the database:
* `.get('ID_OF_ITEM')`: Fetches a single item by ID
* `.getAll()`: Fetches all items from the table
```tsx App.tsx theme={null}
import { useBasic, useQuery } from '@basictech/react'
import { useState } from 'react'
function App() {
// Import db from useBasic() inside your React component
const { db } = useBasic()
// Example 1: Use useQuery to "subscribe" to data from the database
// This will automatically re-render when data changes
const items = useQuery(() => db.collection('tablename').getAll())
// Example 2: Or get a single item by ID using async/await
const [singleItem, setSingleItem] = useState(null)
const getItem = async () => {
const item = await db.collection('tablename').get('ID_OF_ITEM')
if (item) {
setSingleItem(item)
console.log("Successfully fetched item: ", item)
} else {
console.log("Item not found")
}
}
return (
<>
{/* render items */}
>
)
}
```
### Add new item
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { db } = useBasic()
// Use .add() to add items - id is auto-generated
const addItem = async () => {
const newItem = await db.collection('tablename').add({ name: 'cutie' })
console.log("Created item with ID: ", newItem.id)
}
return (
<>
>
)
}
```
### Put (upsert) item
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { db } = useBasic()
// Use .put() to upsert items - requires id in data
const upsertItem = async () => {
const item = await db.collection('tablename').put({
id: 'specific-id-123',
name: 'updated name',
completed: true
})
console.log("Upserted item: ", item)
}
return (
<>
>
)
}
```
### Update item
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { db } = useBasic()
// Use .update() to partially update items
// Only the fields you provide will be changed
const updateItem = async () => {
const updated = await db.collection('tablename').update('ID_OF_ITEM', {
name: 'super cute'
})
if (updated) {
console.log("Successfully updated item: ", updated)
} else {
console.log("Item not found")
}
}
return (
<>
>
)
}
```
### Delete item
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { db } = useBasic()
// Use .delete() to delete items by ID
const deleteItem = async () => {
const deleted = await db.collection('tablename').delete('ID_OF_ITEM')
if (deleted) {
console.log("Successfully deleted item")
} else {
console.log("Item not found")
}
}
return (
<>
>
)
}
```
### Filter items
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
import { useState } from 'react'
function App() {
const { db } = useBasic()
const [completedItems, setCompletedItems] = useState([])
// Use .filter() to filter items client-side
const getCompletedItems = async () => {
const items = await db.collection('todos').filter(
(item) => item.completed === true
)
setCompletedItems(items)
console.log("Completed items: ", items)
}
return (
<>
>
)
}
```
***
# TypeScript Support
The `collection` method supports generics for type-safe database operations:
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
// Define your item type
type Todo = {
id: string
title: string
completed: boolean
createdAt: string
}
function App() {
const { db } = useBasic()
// Type-safe collection access
const todos = db.collection('todos')
const addTodo = async () => {
// TypeScript knows the shape of your data
const newTodo = await todos.add({
title: 'Learn Basic',
completed: false,
createdAt: new Date().toISOString()
})
console.log(newTodo.id) // TypeScript knows this exists
}
return
}
```
# Hooks (useBasic, useQuery)
Source: https://docs.basic.tech/sdk-reference/react-hooks
# useBasic
The `useBasic` hook is used to access authentication, database, and other Basic features.
Any of these properties can be accessed by destructuring the `useBasic` hook.
## Auth State
### isReady
Boolean that indicates when auth state has been determined. Use this as a loading state for your app while auth is being verified.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { isReady, isSignedIn } = useBasic()
if (!isReady) return
Loading...
return (
{isSignedIn ? "User is signed in" : "User is signed out"}
)
}
```
`isAuthReady` is a deprecated alias for `isReady` and will be removed in a future version.
### isSignedIn
Boolean that checks if a user is signed in. Only use after `isReady` is true.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { isReady, isSignedIn } = useBasic()
return (
<>
{isReady ?
{isSignedIn ? "User is signed in" : "User is signed out"}
:
Loading...
}
>
)
}
```
### user
The current user object containing the user's ID, email, and name. Returns `null` when not signed in.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { user } = useBasic()
console.log("Fetch user ID: ", user?.id)
console.log("Fetch user email: ", user?.email)
console.log("Fetch user name: ", user?.name)
return (
<>
{/* render items */}
>
)
}
```
**User type** (fields present depend on IdP / userinfo):
```typescript theme={null}
type User = {
sub?: string
name?: string
email?: string
picture?: string
}
```
### did
The authenticated user's Decentralized Identifier when available; `null` when not signed in or not provided by the identity layer.
### scope
Space-separated OAuth scopes granted on the current access token (for example `profile` plus datastore scopes). Use `hasScope` and `missingScopes` for fine-grained checks.
### hasScope
Returns whether a given scope string is included on the current token.
### missingScopes
Returns scopes your app requested (via provider configuration) that are not yet granted on the current token — useful for prompting re-consent.
## Auth Methods
### signIn
Function to sign a user in via the Basic OAuth flow. It will redirect the user to the Basic login page, and then redirect them back to the page they were on.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { signIn } = useBasic()
return (
<>
>
)
}
```
`signin` (lowercase) is a deprecated alias for `signIn` and will be removed in a future version.
### signInWithHandle
Starts sign-in using a user handle (resolved to a DID server-side). Useful for handle-based login flows in addition to the default `signIn()` redirect.
### signOut
Function to sign a user out. User remains on your page after signing out, but `isSignedIn` will be set to false until they sign in again.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { signOut } = useBasic()
return (
<>
>
)
}
```
`signout` (lowercase) is a deprecated alias for `signOut` and will be removed in a future version.
### signInWithCode
Function to complete authentication using an OAuth authorization code. Useful for custom OAuth flows or React Native apps where you handle the redirect manually.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { signInWithCode } = useBasic()
const handleOAuthCallback = async (code: string, state: string) => {
const result = await signInWithCode(code, state)
if (result.success) {
console.log('Signed in successfully!')
} else {
console.error('Sign in failed:', result.error)
}
}
return <>{/* OAuth callback handling */}>
}
```
**AuthResult type:**
```typescript theme={null}
type AuthResult = {
success: boolean
error?: string
code?: string
}
```
### getToken
Fetches the user's JWT access token. The token is automatically refreshed when needed. Pass `{ forceRefresh: true }` to obtain a new token from the server even if the current one is still valid. Use this for authenticated calls to your backend or the Basic APIs.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { getToken } = useBasic()
const fetchFromMyAPI = async () => {
const token = await getToken()
const response = await fetch('/api/my-endpoint', {
headers: { 'Authorization': `Bearer ${token}` }
})
return response.json()
}
return (
<>
>
)
}
```
### getSignInUrl
Returns the OAuth sign-in URL without redirecting. Useful for custom sign-in flows, React Native apps, or opening in a popup/modal.
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { getSignInUrl } = useBasic()
const handleCustomSignIn = async () => {
const url = await getSignInUrl()
// Open in popup, new tab, or handle as needed
window.open(url, '_blank')
}
return
}
```
`getSignInLink` is a deprecated alias for `getSignInUrl` and will be removed in a future version.
## Development & schema status
### devInfo
Snapshot comparing your local `basic.config` schema to the project schema on the server (project id, version, validity, last check). Intended for debugging and the dev toolbar; `null` if no schema is configured on the provider.
### refreshSchemaStatus
Re-runs the remote schema check and updates `devInfo`. Used by the dev toolbar and custom diagnostics UIs.
## Database
### db
The database object, which you can use to [read, add, update, and delete items](/sdk-reference/react-db).
### dbStatus
The current status of the database connection (string enum). Possible values include:
* `'LOADING'` — Initializing
* `'CONNECTING'` — Connecting to the sync server
* `'ONLINE'` — Connected and syncing
* `'SYNCING'` — Actively syncing
* `'OFFLINE'` — No network (local data may still be available in sync mode)
* `'ERROR_WILL_RETRY'` — Non-fatal error; will retry
* `'ERROR_TOKEN_EXPIRED'` — Auth token expired for sync; refresh or sign in again
* `'ERROR'` — Fatal error
```tsx App.tsx theme={null}
import { useBasic } from '@basictech/react'
function App() {
const { dbStatus } = useBasic()
return (
)
}
```
### dbMode
The current database mode. See [Database Modes](/basic-nextjs/nextjs-modes) for details.
* `'sync'` - Local-first with IndexedDB + real-time WebSocket sync (default)
* `'remote'` - Direct REST API calls to server
***
# useQuery
The `useQuery` hook is used to "subscribe" to data from the database, so that your component automatically re-renders when the data changes. It enables real-time sync between the local database, users' devices, and all other users connected to the same database.
`useQuery` only works in sync mode. In remote mode, use regular async/await patterns instead.
We use it primarily to wrap our `.get()` and `.getAll()` functions, and it takes a function as an argument.
```tsx theme={null}
import { useBasic, useQuery } from '@basictech/react'
function App() {
const { db } = useBasic()
// Example 1: Fetch a single item from the table
const item = useQuery(() => db.collection('tablename').get('ID_OF_ITEM'))
// Example 2: Fetch all items from the table
const items = useQuery(() => db.collection('tablename').getAll())
return (
// render items
)
}
```
The default value is an empty array `[]` while data is loading. This may change in a future version.