# 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 / Signup to Basic Login with your Basic account. If you don't have one yet, signup to Basic. Login / Signup to Basic After creating your Basic account, head to the Dashboard and create a new project. Create your 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. Add your project details This page allows you to view all your projects and manage them. To view project details, click into any of them. Manage your projects 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. Create your schema 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`. Create a new table You can also edit your schema with the code editor. 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. Publish your changes # 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: Schema 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. Schema Tables 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 Schema 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 (
) } ``` *** # 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 ( ) } ``` *** # 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 ( ) } ``` *** # 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 / Signup to Basic Login with your Basic account. If you don't have one yet, signup to Basic. Login / Signup to Basic After creating your Basic account, head to the Dashboard and create a new project. Create your 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. Add your project details This page allows you to view all your projects and manage them. To view project details, click into any of them. Manage your projects 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. Create your schema 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`. Create a new table You can also edit your schema with the code editor. 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. Publish your changes # 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: Schema 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. Schema Tables 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 Schema 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 ```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 postcss autoprefixer npx tailwindcss init -p ``` Update `tailwind.config.js`: ```js tailwind.config.js theme={null} /** @type {import('tailwindcss').Config} */ export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], } ``` Replace `src/index.css` with: ```css src/index.css theme={null} @tailwind base; @tailwind components; @tailwind utilities; ``` ```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 (

my lofi to-do app

setTaskInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && addTask()} />
{isSignedIn ? (

Signed in as: {user?.email}

) : ( )}
) } export default App ```
Add `useQuery` to display tasks and a delete function: ```tsx src/App.tsx theme={null} import { useBasic, useQuery } from '@basictech/react' import { useState } from 'react' import './App.css' function App() { const { signIn, signOut, isSignedIn, user, db } = useBasic() const [taskInput, setTaskInput] = useState('') // Subscribe to all todos - auto-updates when data changes const tasks = useQuery(() => db.collection('todos').getAll()) const addTask = async () => { if (!taskInput.trim()) return await db.collection('todos').add({ name: taskInput, completed: false }) setTaskInput('') } const deleteTask = async (id: string) => { await db.collection('todos').delete(id) } return (

my lofi to-do app

setTaskInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && addTask()} />
{/* Task list */}
{tasks?.map((task: any) => (
{task.name}
))}
{isSignedIn ? (

Signed in as: {user?.email}

) : ( )}
) } 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 / Signup to Basic Login with your Basic account. If you don't have one yet, signup to Basic. Login / Signup to Basic After creating your Basic account, head to the Dashboard and create a new project. Create your 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. Add your project details This page allows you to view all your projects and manage them. To view project details, click into any of them. Manage your projects 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. Create your schema 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`. Create a new table You can also edit your schema with the code editor. 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. Publish your changes # 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: Schema 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. Schema Tables 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 Schema 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 more details, read about [Basic Auth](/readings/auth-basic#introduction-to-oauth-2-0). 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 on [required parameters](/readings/auth-basic#required-parameters)): * `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 [Basic Auth](/readings/auth-basic) for the full OAuth flow. ```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 / Signup to Basic Login with your Basic account. If you don't have one yet, signup to Basic. Login / Signup to Basic After creating your Basic account, head to the Dashboard and create a new project. Create your 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. Add your project details This page allows you to view all your projects and manage them. To view project details, click into any of them. Manage your projects 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. Create your schema 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`. Create a new table You can also edit your schema with the code editor. 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. Publish your changes # 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.
Hero Light
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 [Why Basic?](/readings/why-basic)). * **Built-in auth and permissioning**: OAuth flows and scoped tokens, with row-level expectations documented in [Managing permissions](/readings/permissioning). * **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 **Documentation** dropdown: | 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 # Basic Auth Source: https://docs.basic.tech/readings/auth-basic Overview of the Basic Auth This is primarily for developers using the Basic API. The client SDKs handle all these aspects of OAuth 2.0 for you. # Introduction to OAuth 2.0 Basic uses OAuth 2.0 for authentication. This widely adopted framework allows users to securely grant permissions to multiple applications using a single account, similar to "Login with Google". It ensures user data security while enabling controlled access for third-party apps, without directly handling sensitive login credentials. Basic Auth Flow ### Authorization First, an application redirects the user to the Authorization Server. Here, the user enters their login credentials of their existing account (e.g., Google, Apple, BasicID, etc.), and explicitly grants permissions to your app. The benefit of using a service for Authorization is that they handle storing user credentials securely, validating passwords, account recovery, etc. The Authorization Server then issues an authorization code to the application, which has a one-time use and is exchanged for an access token (also known as JWTs) and a refresh code. These tokens and codes should be extracted from the callback URL and are generally stored in your user's cache or cookies. ### Access tokens Access tokens are a short-lived JWT (with Basic, this is 1 hour) that can be used to make secure and authenticated requests to protected information. They are used to make requests to the API via the `Authorization` header (e.g., `Authorization: Bearer 'YOUR_ACCESS_TOKEN'`). Before making a request, you should validate if the access token is still valid - if it has expired, you would use the refresh token to get a new access token without requiring the user to re-authenticate (i.e., without re-entering their login credentials). ### Refresh code Refresh codes can be used to obtain a new access token via the `/token` endpoint when the current access token expires. This regenerates a new access token and refresh code. *** # Required parameters Here are the required parameters you need to set on the "Sign in with Basic" button: * `client_id` * `redirect_uri` * `response_type` * `scope` * `state` ### client\_id Your application's client ID from [admin.basic.tech](https://admin.basic.tech) (project settings). ### redirect\_uri This is the URL that the user will be redirected to after they sign up or sign in. This would generally be a URL on your app. ### response\_type This must always be set to `code`, so that you can receive and extract an authorization code in the callback URL. ### scope Scopes are used to specify the information that your application requires. For Basic, you can specify `profile` to request the user's email address and other information. ### state The state parameter is used to prevent CSRF attacks. It is a random string that is used to verify that the callback request is legitimate. It is recommended that you use a secure random string generator to generate a state parameter, and that you verify that the state parameter that you receive in the callback URL is the same as the one you sent. ### PKCE (optional, recommended) SHA-256 hash of your `code_verifier`, base64url-encoded. Send together with `code_challenge_method=S256` on `/auth/authorize`, then send the original `code_verifier` in the body of `POST /auth/token` when exchanging the code. The [REST quickstart](/basic-restapi/basic-api) and the [redirect-to-sign-in API reference](/api-reference/auth/redirect-to-sign-in) describe the full parameter set. # Local-first sync Source: https://docs.basic.tech/readings/local-first-sync How Basic's clients keep data fast, offline-friendly, and in sync. # Summary Local-first architecture refers to a modern development pattern where primary read and write operations occur to a local database on clients' devices while data is synced across devices and users automatically in the background. This provides benefits like instant UI, offline support, and real-time capabilities while still syncing to the cloud automatically. With Basic, you get these behaviors from the **sync** mode of our client SDKs (for example [React](/basic-react/basic-react-sdk) or [Next.js](/basic-nextjs/basic-nextjs-sdk)). **Remote** mode talks to the server directly without a local replica—see [Database modes](/basic-nextjs/nextjs-modes). # What is local-first sync? Local-first sync Local-first architecture is a hybrid approach that believes in leveraging the capabilities of client devices to enhance the cloud experience, rather than solely relying on the cloud. This is in direct juxtaposition to local-only or cloud-only architectures. | Native property | Local-only | Local-first | Cloud | | ------------------------ | :--------: | :---------: | :---: | | Low-latency / Instant UI | ✓ | **✓** | | | Offline support | ✓ | **✓** | | | Real-time sync | | **✓** | ✓ | | Multi-device support | | **✓** | ✓ | | Backup to cloud | | **✓** | ✓ | With the rise of really powerful web apps that demand more responsive UIs while needing to be highly collaborative (such as Figma, Linear, Notion, etc.), local-first sync has become a popular approach for both adopters and tool makers. Figma Linear ElectricSQL Pierre Zero # Benefits of local-first sync ### Instant and snappy UI Regular UI Instant UI One of the immediately noticeable benefits of local-first sync is how snappily the UI responds to user actions. For example, in the Regular UI, there's a small delay between when a user clicks the "Add" button and when the UI updates to reflect the new item. This is because the action requires a network request to the server to update the database, for the client to receive the new state, and the UI to re-render. In the Instant UI example, the UI updates immediately after the user clicks the "Add" button because the action only requires updating the local database. Changes are synced to the cloud in the background without any visible latency to the user. ### Fewer network calls Fewer network calls In a local-first sync environment, every primary read/write operation occurs directly to the local database, drastically reducing the number of network calls to the server. This change in data operations has two primary benefits: Fewer network calls across all your users mean a lower load on the server, therefore lowering server costs. Having to rely on the network for primary read/write operations introduces many points of failure. Local-first sync overcomes issues like lost connectivity, unreachable servers, and slow networks that often make apps unusable. Instead, local-first architectures generally leverage a continuous connection (e.g., Basic uses WebSocket) to the server to sync data. ### Simpler architecture Simpler architecture Adopting the Basic local-first sync engine abstracts the need for a bloated backend. Traditional stacks require spinning up and maintaining a backend to make API calls to the database. In contrast, you can use the Basic local-first sync architecture to have your app's frontend interact directly with your client's local database while the Basic sync engine manages cloud database communication. The simpler architecture translates to faster development cycles for your developers with less backend code to maintain. ### Automatic state management Manual state management Automatic state management Traditionally, developers need to update state with the data fetched from their API requests. Basic's client SDKs contain hooks that listen for changes and automatically update state on the frontend. This translates to significantly less code for developers to write, test, and maintain as is evident from the example above. Now, you can access data as simply as `db.collection('tablename').get()` without worrying about updating state. ### Offline support Offline support Connection gets spotty for users in various situations - if they're going through a tunnel, hiking through a less populated park, or even just in a crowded coffee shop. Rather than eliminating your users' ability to use your app in these situations, local-first sync ensures they can continue working with your app. This can reduce their frustration and save your developers the time of implementing loading states for every UI element they build. ### Real-time capabilities Real-time capabilities The future of the internet has proven itself to be collaborative. But building real-time capabilities is very hard and takes away your developers' time from building other features. Basic's local-first sync engine comes with built-in real-time capabilities so your users can use your app with their friends and coworkers with no additional engineering on your part. # Trade-offs with local-first sync It would be insincere to discuss a new technology without mentioning its trade-offs. Here are a few we've identified: Local-first apps require more storage on the client device as they store more data locally. As more of the internet becomes local-first, more storage will be required to support this. However, storage has gotten really cheap, with phones and laptops coming with higher storage capacities by default. Additionally, good partial-sync algorithms (as those provided by Basic) manage this automatically on behalf of developers and users. Local-first apps offload a lot of primary read/write operations to the client device. While most modern devices are more than capable of handling this, some older devices or devices with limited processing power may face faster battery drain. Basic's sync engine is designed to be efficient in terms of processing power, and we regularly make optimizations to ensure that your app remains fast and responsive even as its data grows. # How to use local-first with Basic All of Basic's local-first sync capabilities come built-in with our client SDKs. Start with the [React](/basic-react/basic-react-sdk) or [Next.js](/basic-nextjs/basic-nextjs-sdk) quickstart (sync mode), or use the [REST API](/basic-restapi/basic-api) when you are not using a local-first client. # 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. # Managing permissions Source: https://docs.basic.tech/readings/permissioning How users will be able to manage permissions to their data Permission UX and fine-grained scope strings continue to evolve. Treat this page as the conceptual model; exact scope names and dashboard controls may change—verify against your project settings and the live OAuth flow. # How it works Users sign in and **grant scopes** to your app. Access tokens encode what the app may do. Users can **revoke** access from their account when they no longer trust an app. ### Flow 1. Application initiates login / auth flow, which redirects users to a Basic SSO page 2. User can select how much access they want to give 3. The scope is defined into a self-verifiable token (JWT) that is sent to the application The token is cryptographically signed and is verifiable (i.e., it cannot be forged, and developers can independently verify that it came from the respective users) 4. The application then uses this token to make requests to the user's datastore. The application simply includes the token as an auth header in its API requests ### Scope Usually, an application will specify what it wants to request. At minimum, this is set to `profile` to return general profile info. Scope can be extended to include access to the database, which is organized in the following hierarchy: `database > table > row > field`. For example, an application might request the following scope: * `profile` * `database_id.table_id` = read * `database_id.table_id.row_id` = read, write In this example, the application can read anything under a specified table in the database, but only has write access to one row/object. This process is modeled after [UCANs](http://ucan.xyz/) # Personal Data Stores (PDS) Source: https://docs.basic.tech/readings/personal-data-stores What is a PDS in Basic # Summary Basic Data Store Basic Apps PDS' are independent cloud data stores per user. An assigned primary user is the ultimate owner of the data within that data store. Data may be shared with apps, devices, and other users, and the decision to do so is always made by (or reverted by) the primary user. # What is a Basic Data Store? We designed Basic Data Stores so that every user is the proprietor of their own data. Personal Data Store The data store above is depicted for an individual. For businesses, the data store would be owned by primary admins and the same principles apply. Fundamentally, a PDS is a data store that’s dedicated to a user. This database is independent of any application that it is connected to (in that it doesn’t live on the servers or be owned by applications in the way a traditional database like Firebase, MongoDB, etc. may be). Instead, it’s ownership belongs to the user that’s assigned to it as a primary user. This contrasts with the traditional centralized ownership model of data as shown below: Centralized Data Ownership It’s best to think of a database as a bucket of data. In a centralized model, whoever is building an app (they may use databases like Firebase, MongoDB, etc.) holds onto that bucket of data, putting in all the information about their users, and the data generated by their users. The developers of that app can do with that bucket as they see fit, and at the end of the day, they own that bucket and its contents. For example, if your user is typing up a document, they are not only using your interface (frontend) to type and format the document, but also using your backend and data layers to store the data. Data flows between the frontend, backend, and data layers as shown in App A. The apps and websites they're using act as an intermediary that they have to interact with in order to retrieve or change their own data. For example, if App B or C want to access the words they had typed up in Google Docs (App A), they would likely have to go through the Google Docs backend. PDS' flips this paradigm and give every user their own bucket, where information about them and the data they generate is put into their own independent buckets. Applications will request permission to read / write data to those buckets, but at any point users can sever those connections to any of their apps rather than relying on intermediary services for accessing their data. # Benefits of PDS' ### Consolidation and a single user datastore Consolidation and a single user datastore In traditional centralized data ownership models, user data is spread across multiple databases owned by different apps and websites (notice how User A's data is spread across Apps A, B, and C). The data is likely to be in different formats, limiting its utility and increasing the complexity of integration. With PDS', user data remains in a single space that can be queried (all the data from Apps A, B, and C that pertains to User A are stored in User A's data store). If you were to build a word processor tool A (similar to Google Docs) using Basic, all the words that your users type up on the document are stored in their data store. Any words they may type up in a different word processor tool B that also uses Basic for data would also be stored in the same data store. Apps read and write only the collections and fields users authorize. Multiple apps can connect to the same user’s store when the user allows it, which helps **reuse context** (for example profile or notes) without each app holding a separate siloed copy—exact capabilities depend on schema and permissions. ### Control, ownership, and permanence Control, Ownership, and Permanence Traditionally, user data is generally only interactable through APIs of the respective services. This approach relies on individual services to implement APIs, and to decide what data to share or not. A mismatch of priorities between the type of data a service wants to make available through their APIs and the data a user may want to use would cause users to feel like their data is "walled off". With PDS', a user's data remains with them regardless of the availability of APIs or the shuttering of services (in the diagram above, even if any of the connections to apps, devices, or users disappear, the data will still be available in User A's data store). It will remain available for use with any future services - a deep dive into how Basic enables data permanence is coming soon. ### Simpler applications Simpler Apps Traditional apps and websites have larger and more complex backend and data layers because of having to manage data, auth, and other complex data operations for all their users. PDS' allow developers to build apps and websites with much simpler architectures since they no longer need to own or manage user data. With Basic, developers can focus on building better user experiences. # FAQs No. Human errors and social engineering compose of the vast majority of security breaches. In traditional databases, the consequences of these sort of human-oriented attacks are much more dire since a single database tends to contain information about hundreds of thousands or millions of users. In PDS, data is limited to only that of a single user. For a hacker to obtain the equivalent scale of data, they’d have to conduct an attack on millions of users instead of relying on a single “weak point”, which is significantly more difficult and less rewarding for them. On top of that, Basic’s protocol is designed to be safe and our user’s information is of our utmost priority and will continue to invest in ensuring they don’t fall privy to phishing or data breach attempts. No. Basic automatically syncs data across devices and the cloud when internet is available to ensure that data is always backed up. Users don’t have to manually perform any backups of their data. No, Basic is simple to use. Users can manage their apps and permissions on Basic.id (feel free to check out what the experience would look like for them at [Basic.id](https://basic.id)!). The interface to do so is simple and straightforward, and we’ll continue to invest in making this experience smoother for users. In fact, if they never intend on changing any of their permissions, other than the OAuth flow to sign up to your app they never have to interact with Basic directly since it’s entirely optional. And they can always in the future choose to access their data since it’s always in their control from the start even if they don’t take any explicit actions towards it - Basic is designed to retain data ownership for its users. *** Basic provides open-source-oriented tooling (auth, datastore APIs, sync clients) so you can build applications where **user data stays user-centric** and permissioned, without standing up all of that infrastructure yourself. # Schema management Source: https://docs.basic.tech/readings/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. Schemas follow this general format: Schema 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. Schema Tables 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 Schema 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 } } } } } ``` # Why Basic? Source: https://docs.basic.tech/readings/why-basic hero Basic is a backend for **user-owned data**: each end user gets a dedicated datastore in the cloud, with OAuth sign-in, sync, and clear permission boundaries. Apps read and write that data through SDKs or HTTP APIs instead of running a separate database for every customer. It fits products that need **personalization, cross-app context, or agents**—anywhere user-specific state should stay portable and under the user’s control. ## Problem Typical app backends centralize user data with the vendor: * **Fragmentation**: The same person’s data is copied and isolated per app. * **Lock-in**: Hard to move data or reuse it across tools the user already uses. * **Privacy and risk**: One breach or policy change affects many users at once. * **Operational load**: You scale storage, backups, and compliance for all users’ rows in one place. ## What we optimize for 1. **User-owned stores**: Data is tied to the user; apps request access instead of owning the canonical copy. 2. **Portability**: Users can bring the same datastore to multiple apps that integrate Basic, within the permissions they grant. 3. **Built-in auth and scopes**: Sign-in and tokens reflect what each app is allowed to do. 4. **Developer speed**: Schema-as-code, client SDKs with local-first sync where supported, and REST for any stack. We also care that the platform is **easy to adopt** (quick project setup, familiar document-style collections), **typed at the schema layer**, and **elastic** as your user base grows. *** ## How Basic differs Traditionally, your app hosts one database and all user rows live there: traditional diagram With Basic, each user has their own datastore; your app uses APIs and SDKs against that federated model: basic diagram ### For users 1. **Control**: Revoke or narrow app access when they want. 2. **One place for their data**: Fewer duplicate profiles and silos. 3. **Sync and offline** (with client SDKs): Work locally; sync when online. 4. **Less vendor dependency**: Their data isn’t only inside one app’s servers. ### For developers 1. **Less central DB to scale**: Heavy reads/writes move to a model aligned with per-user stores. 2. **Security posture**: Sensitive payloads can stay scoped to user stores and tokens. 3. **Faster integration**: Auth, schema, and CRUD patterns are consistent across React, Next.js, Expo, and REST. ## How it works ### Auth OAuth 2.0 (with PKCE where applicable). Users sign in through Basic; your app receives tokens with agreed scopes. Client SDKs wrap the flow; for custom stacks, see [Basic Auth](/readings/auth-basic). ### Data model Today, projects define **collections** of JSON-shaped documents in `basic.config` (similar in spirit to Firebase or MongoDB collections), with indexed fields for sync and queries. The important difference is **per-user isolation** and **local-first clients** where you use the sync SDK. *** For a product-focused overview, start with [What is Basic?](/get-started/welcome). For architecture detail, see [Personal data stores](/readings/personal-data-stores) and [Local-first sync](/readings/local-first-sync). # 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 (
Database status: {dbStatus} {dbStatus === 'OFFLINE' && (Working offline)}
) } ```
### 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.