# 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 Use Node.js 24 LTS for this guide. The Basic React SDK requires Node.js 24 or newer. ```bash Terminal theme={null} npm create vite@latest my-todo-app -- --template react-ts cd my-todo-app npm install ``` ```bash Terminal theme={null} npm install @basictech/react npm install -D tailwindcss@^4 @tailwindcss/vite@^4 ``` Add Tailwind's Vite plugin alongside the React plugin in `vite.config.ts`: ```ts vite.config.ts theme={null} import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [react(), tailwindcss()], }) ``` Replace `src/index.css` with: ```css src/index.css theme={null} @import "tailwindcss"; ``` Tailwind 4 detects your source files automatically. You do not need a `tailwind.config.js`, PostCSS configuration, or an `init` command for this setup. ```bash Terminal theme={null} npm run dev ``` Navigate to `http://localhost:5173` to see your app running. ## Create a simple To-Do UI Replace the code in your `src/App.tsx` file: ```tsx src/App.tsx theme={null} import './App.css' function App() { return (

my lofi to-do app

) } export default App ```
## Connect to a Basic project Go to [admin.basic.tech](https://admin.basic.tech) and create a free account. Then create a new project and copy your Project ID. Create a `basic.config.ts` file in your project root: ```ts basic.config.ts theme={null} export const schema = { project_id: "YOUR_PROJECT_ID", // Replace with your project ID version: 0, tables: { todos: { type: "collection", fields: { name: { type: "string" }, completed: { type: "boolean" } } } } } ``` Update `src/main.tsx`: ```tsx src/main.tsx theme={null} import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { BasicProvider } from '@basictech/react' import { schema } from '../basic.config' import App from './App' import './index.css' createRoot(document.getElementById('root')!).render( , ) ``` ## Add sign in and sign out functionality Update `src/App.tsx`: ```tsx src/App.tsx theme={null} import { useBasic } from '@basictech/react' import './App.css' function App() { const { signIn, signOut, isSignedIn, user } = useBasic() return (

my lofi to-do app

{isSignedIn ? (

Signed in as: {user?.email}

) : ( )}
) } export default App ```
## Hook up to Basic DB Update `src/App.tsx` to add state and the `addTask` function: ```tsx src/App.tsx theme={null} import { useBasic } from '@basictech/react' import { useState } from 'react' import './App.css' function App() { const { signIn, signOut, isSignedIn, user, db } = useBasic() const [taskInput, setTaskInput] = useState('') const addTask = async () => { if (!taskInput.trim()) return await db.collection('todos').add({ name: taskInput, completed: false }) setTaskInput('') } return (

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 request details, see the [sign-in API reference](/api-reference/auth/redirect-to-sign-in). In this guide, you will: 1. Use "redirect to sign in" endpoint to redirect users to account creation / login flow 2. Extract code from URL 3. Use "get auth token" endpoint with extracted code 4. Store auth token object for future API calls Create a button that redirects the user to the Basic Auth page, with the following required parameters (more info in the [sign-in API reference](/api-reference/auth/redirect-to-sign-in)): * `client_id='YOUR_CLIENT_ID'` * `redirect_uri='YOUR_REDIRECT_URI'` * `response_type=code` * `scope=profile` * `state='YOUR_STATE'` Redirect URL template: `https://api.basic.tech/auth/authorize?response_type=code&redirect_uri=YOUR_REDIRECT_URI&scope=profile&state=YOUR_STATE&client_id=YOUR_CLIENT_ID` Replace `YOUR_CLIENT_ID`, `YOUR_REDIRECT_URI`, and `YOUR_STATE` with your own values. `scope` must be a space-separated list (for example `profile` alone, or `profile` plus datastore scopes your app needs). The live contract matches the [OpenAPI spec](/api-reference/auth/redirect-to-sign-in) (`GET /auth/authorize`). **PKCE (recommended):** You may include `code_challenge` and `code_challenge_method=S256` on the authorize request and the matching `code_verifier` when exchanging the code at `/auth/token`. See the [sign-in](/api-reference/auth/redirect-to-sign-in) and [token](/api-reference/auth/get-auth-token) API references for request details. ```bash cURL theme={null} curl --request GET \ --url 'https://api.basic.tech/auth/authorize?response_type=code&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'&client_id='YOUR_CLIENT_ID'' ``` ```python Python theme={null} import requests url = "https://api.basic.tech/auth/authorize" querystring = {"response_type":"code","client_id":"YOUR_CLIENT_ID","redirect_uri":"YOUR_REDIRECT_URI","scope":"profile","state":"YOUR_STATE"} response = requests.request("GET", url, params=querystring) print(response.text) ``` ```javascript JavaScript theme={null} const options = {method: 'GET'}; fetch('https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'', options) .then(response => response.json()) .then(response => console.log(response)) .catch(err => console.error(err)); ``` ```php PHP theme={null} "https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "GET", ]); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if ($err) { echo "cURL Error #:" . $err; } else { echo $response; } ``` ```go Go theme={null} package main import ( "fmt" "net/http" "io/ioutil" ) func main() { url := "https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'" req, _ := http.NewRequest("GET", url, nil) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```java Java theme={null} HttpResponse response = Unirest.get("https://api.basic.tech/auth/authorize?response_type=code&client_id='YOUR_CLIENT_ID'&redirect_uri='YOUR_REDIRECT_URI'&scope=profile&state='YOUR_STATE'") .asString(); ``` The authorization code will be in the URL as a `code` query parameter. For example: `https://your-app.com/callback?code=1234567890`. You need to extract the code from the URL and use it in the next step. ```bash cURL theme={null} # Assuming the URL is stored in a variable URL="https://your-app.com/callback?code=1234567890" CODE=$(echo $URL | grep -o 'code=[^&]*' | cut -d'=' -f2) echo $CODE ``` ```python Python theme={null} from urllib.parse import urlparse, parse_qs # Example URL url = "https://your-app.com/callback?code=1234567890" # Parse the URL and extract the code parameter parsed_url = urlparse(url) code = parse_qs(parsed_url.query)['code'][0] print(code) ``` ```javascript JavaScript theme={null} // Example URL const url = "https://your-app.com/callback?code=1234567890"; // Extract the code parameter const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); console.log(code); ``` ```php PHP theme={null} ``` ```go Go theme={null} package main import ( "fmt" "net/url" ) func main() { // Example URL rawURL := "https://your-app.com/callback?code=1234567890" // Parse the URL and extract the code parameter parsedURL, _ := url.Parse(rawURL) code := parsedURL.Query().Get("code") fmt.Println(code) } ``` ```java Java theme={null} import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { // Example URL String url = "https://your-app.com/callback?code=1234567890"; // Parse the URL and extract the code parameter String query = URI.create(url).getQuery(); Map params = new HashMap<>(); for (String param : query.split("&")) { String[] pair = param.split("="); params.put(pair[0], URLDecoder.decode(pair[1], StandardCharsets.UTF_8)); } String code = params.get("code"); System.out.println(code); } } ``` Make a post request to `/auth/token` with the authorization code in the body. The response will be a JSON object with the access token and a refresh code. Token URL template: `https://api.basic.tech/auth/token` Make sure to replace `YOUR_CODE` with your own values ```bash cURL theme={null} curl --request POST \ --url 'https://api.basic.tech/auth/token' \ --header 'Content-Type: application/json' \ --data '{"code":"YOUR_CODE"}' ``` ```python Python theme={null} import requests url = "https://api.basic.tech/auth/token" payload = {"code": "YOUR_CODE"} headers = {"Content-Type": "application/json"} response = requests.request("POST", url, json=payload, headers=headers) print(response.text) ``` ```javascript JavaScript theme={null} const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({code: 'YOUR_CODE'}) }; fetch('https://api.basic.tech/auth/token', options) .then(response => response.json()) .then(response => console.log(response)) ``` ```php PHP theme={null} "https://api.basic.tech/auth/token", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => "{\n \"code\": \"YOUR_CODE\"\n}", CURLOPT_HTTPHEADER => [ "Content-Type: application/json" ], ]); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if ($err) { echo "cURL Error #:" . $err; } else { echo $response; } ``` ```go Go theme={null} package main import ( "fmt" "strings" "net/http" "io/ioutil" ) func main() { url := "https://api.basic.tech/auth/token" payload := strings.NewReader("{\n \"code\": \"YOUR_CODE\"\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```java Java theme={null} HttpResponse response = Unirest.post("https://api.basic.tech/auth/token") .header("Content-Type", "application/json") .body("{\n \"code\": \"YOUR_CODE\"\n}") .asString(); ``` The response from the token endpoint will look like this: ```json theme={null} { "access_token": "", "token_type": "", "expires_in": 123, "refresh_token": "" } ``` # Basic database APIs guide The [database APIs](/api-reference/project-users/create-item) are easy to use. Make sure to check the validity of the `access_token`, and pass it in the Header of your API call. If the `access_token` is expired, you'd repeat step 3 of the [Auth APIs guide](/basic-restapi/basic-api#basic-auth-apis-guide) but using the `refresh_token` instead of the extracted code. This will return you a fresh new `access_token`. Here's how to validate and use the access token in different programming languages: ```bash cURL theme={null} # Note: For bash, you'll need to use a JWT decoding tool like jq or jwt-cli # Example using jwt-cli: # if jwt decode YOUR_ACCESS_TOKEN | jq '.exp > now' | grep -q "true"; then # # Token is valid, proceed with API calls curl --request GET \ --url 'https://api.basic.tech/your-endpoint' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' # else # # Token is expired, refresh it curl --request POST \ --url 'https://api.basic.tech/auth/token' \ --header 'Content-Type: application/json' \ --data '{"refresh_token":"YOUR_REFRESH_TOKEN"}' ``` ```python Python theme={null} import jwt import time import requests def refresh_token(refresh_token): response = requests.post( 'https://api.basic.tech/auth/token', json={'refresh_token': refresh_token}, headers={'Content-Type': 'application/json'} ) return response.json() def is_token_valid(access_token): try: # Decode the token without verification decoded = jwt.decode(access_token, options={"verify_signature": False}) # Check if token is expired return decoded['exp'] > time.time() except: return False # Example usage if is_token_valid(access_token): # Token is valid, proceed with API calls headers = {'Authorization': f'Bearer {access_token}'} response = requests.get('https://api.basic.tech/your-endpoint', headers=headers) else: # Token is invalid, refresh it new_tokens = refresh_token(refresh_token) access_token = new_tokens['access_token'] refresh_token = new_tokens['refresh_token'] # Now use the new access token headers = {'Authorization': f'Bearer {access_token}'} response = requests.get('https://api.basic.tech/your-endpoint', headers=headers) ``` ```javascript JavaScript theme={null} import jwtDecode from 'jwt-decode'; async function refreshToken(refreshToken) { const response = await fetch('https://api.basic.tech/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }) }); return await response.json(); } function isTokenValid(accessToken) { try { const decoded = jwtDecode(accessToken); // Check if token is expired return decoded.exp * 1000 > Date.now(); } catch (error) { return false; } } // Example usage if (isTokenValid(accessToken)) { // Token is valid, proceed with API calls const response = await fetch('https://api.basic.tech/your-endpoint', { headers: { 'Authorization': `Bearer ${accessToken}` } }); } else { // Token is invalid, refresh it const newTokens = await refreshToken(refreshToken); accessToken = newTokens.access_token; refreshToken = newTokens.refresh_token; // Now use the new access token const response = await fetch('https://api.basic.tech/your-endpoint', { headers: { 'Authorization': `Bearer ${accessToken}` } }); } ``` ```php PHP theme={null} $refreshToken ])); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } function isTokenValid($accessToken) { try { $decoded = JWT::decode($accessToken, null, false); return $decoded->exp > time(); } catch (Exception $e) { return false; } } // Example usage if (isTokenValid($accessToken)) { // Token is valid, proceed with API calls $ch = curl_init('https://api.basic.tech/your-endpoint'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken ]); $response = curl_exec($ch); curl_close($ch); } else { // Token is invalid, refresh it $newTokens = refreshToken($refreshToken); $accessToken = $newTokens['access_token']; $refreshToken = $newTokens['refresh_token']; // Now use the new access token $ch = curl_init('https://api.basic.tech/your-endpoint'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken ]); $response = curl_exec($ch); curl_close($ch); } ?> ``` ```go Go theme={null} package main import ( "fmt" "time" "net/http" "io/ioutil" "encoding/json" "github.com/golang-jwt/jwt/v5" ) type TokenResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } func refreshToken(refreshToken string) (*TokenResponse, error) { payload := map[string]string{ "refresh_token": refreshToken, } jsonData, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://api.basic.tech/auth/token", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var tokenResp TokenResponse err = json.NewDecoder(resp.Body).Decode(&tokenResp) return &tokenResp, err } func isTokenValid(accessToken string) bool { token, _, err := jwt.NewParser().ParseUnverified(accessToken, jwt.MapClaims{}) if err != nil { return false } if claims, ok := token.Claims.(jwt.MapClaims); ok { exp := claims["exp"].(float64) return int64(exp) > time.Now().Unix() } return false } func main() { if isTokenValid(accessToken) { // Token is valid, proceed with API calls req, _ := http.NewRequest("GET", "https://api.basic.tech/your-endpoint", nil) req.Header.Add("Authorization", "Bearer "+accessToken) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) } else { // Token is invalid, refresh it newTokens, err := refreshToken(refreshToken) if err != nil { fmt.Println("Error refreshing token:", err) return } accessToken = newTokens.AccessToken refreshToken = newTokens.RefreshToken // Now use the new access token req, _ := http.NewRequest("GET", "https://api.basic.tech/your-endpoint", nil) req.Header.Add("Authorization", "Bearer "+accessToken) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) } } ``` ```java Java theme={null} import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import java.util.Date; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import com.google.gson.Gson; class TokenResponse { String access_token; String refresh_token; } public class TokenValidator { private static final HttpClient client = HttpClient.newHttpClient(); private static final Gson gson = new Gson(); public static TokenResponse refreshToken(String refreshToken) throws Exception { String json = String.format("{\"refresh_token\":\"%s\"}", refreshToken); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.basic.tech/auth/token")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return gson.fromJson(response.body(), TokenResponse.class); } public static boolean isTokenValid(String accessToken) { try { Claims claims = Jwts.parserBuilder() .build() .parseClaimsJwt(accessToken) .getBody(); Date expiration = claims.getExpiration(); return expiration.after(new Date()); } catch (Exception e) { return false; } } public static void main(String[] args) { if (isTokenValid(accessToken)) { // Token is valid, proceed with API calls HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.basic.tech/your-endpoint")) .header("Authorization", "Bearer " + accessToken) .build(); try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } catch (Exception e) { e.printStackTrace(); } } else { // Token is invalid, refresh it try { TokenResponse newTokens = refreshToken(refreshToken); accessToken = newTokens.access_token; refreshToken = newTokens.refresh_token; // Now use the new access token HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.basic.tech/your-endpoint")) .header("Authorization", "Bearer " + accessToken) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } catch (Exception e) { e.printStackTrace(); } } } } ``` # Set up your Basic account Source: https://docs.basic.tech/get-started/adminportal Using the admin portal to create a new project. # Create your project Head to [https://app.basic.tech](https://app.basic.tech) to begin creating your project. Login / 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 [The PDS](/readings/personal-data-stores)). * **Built-in auth and permissioning**: Sign-in and app approval let users choose which apps can access their data (see [Auth](/readings/auth-basic)). * **Federated, per-user datastores**: Portable, user-centric data for deep personalization. * **Responsive clients**: Local-first sync where you use the web/mobile SDKs; near-instant UI with offline-friendly patterns. *On the roadmap: broader real-time collaboration patterns, federated learning, and more.* ## Use cases * **AI apps and agents**: Store and retrieve user context across sessions safely. * **AI app builders**: Hand thousands of end-user apps a consistent auth + data layer. * **AI wearables and sensitive UX**: Keep user data scoped to their store without leaking it across tenants. * **Embedded AI**: Personalize models using permissioned user context, not only generic defaults. * **Personal and productivity software**: Sync and offline-friendly UX for competitive polish. ## Getting started First, [set up your Basic account and project](/get-started/adminportal). Optionally [install the Basic CLI](/get-started/cli). Then pick docs for your stack from the dropdown in the **Build** tab: | Framework / API | Support | | ------------------- | :-----: | | React | Yes | | Next.js | Yes | | Expo / React Native | Yes | | REST | Yes | | Management APIs | Yes | *More client runtimes are on the [roadmap](/readings/others/roadmap).* **Implementation guides:** Integrate Basic into your React project Integrate Basic into your Next.js project Integrate Basic into your Expo / React Native project Integrate Basic over HTTP ## Side quests Core concepts behind user-owned data Feedback or help getting started Product updates # Auth Source: https://docs.basic.tech/readings/auth-basic One account. You choose which apps to connect. If your data has a home of its own, apps need a way to know it's yours—and whether you've invited them in. That's what auth is for. ## Signing in and connecting an app Your Basic account is your identity across apps built with Basic. You can use the same account rather than starting from scratch in each app. There are two separate choices: * **Signing in** confirms which account is yours. * **Approving an app** lets it work with the data it's allowed to use. Signing in alone doesn't open your whole PDS to an app. A notes app can work with your notes without getting access to unrelated information. ## You can change your mind You can disconnect an app when you no longer want it to have access. The data it saved in your PDS stays there. This separates your account from any one app. You aren't handing over your data's home every time you try a new tool—you're choosing who can use it. # Sync Source: https://docs.basic.tech/readings/local-first-sync Work on your device. Keep your data together. An app should feel quick when you use it, and your work should be there when you switch devices. Sync connects those two experiences. ## Work here, catch up everywhere Apps using Basic's sync keep a working copy of your data on your device. Changes are saved there first, so you don't have to wait for the internet after every action. This is called **local-first**. When connected, the app sends changes to your PDS and receives updates made elsewhere. Your PDS keeps the main version that your devices catch up with. For example, you can edit a downloaded note while offline. When you reconnect and it syncs, that edit becomes available on your other devices. Until then, it only exists on the device where you made it. ## The internet can wait—sometimes Offline work depends on what an app keeps on your device. Not every feature works offline, and working with someone else's shared data may still need a connection. If two edits can't be combined safely, the app may need to help resolve them. Sync keeps changes moving between devices; it can't always decide what you meant. # Multiplayer & sharing Source: https://docs.basic.tech/readings/multiplayer-and-sharing Work together without giving up ownership. Personal data doesn't have to mean solitary data. Some things are more useful together: a shopping list, a trip plan, or a collection of ideas. ## An invitation to the same data Sharing lets you invite another person to selected data. They accept the invitation and can view it, or help edit it if you've allowed that. The data stays in the owner's PDS. The other person gets access to it—not a separate copy in their own PDS, and not access to everything you own. ## Working together When people can see each other's changes and work on the same information, we call that **multiplayer**. Imagine a shared shopping list. One person adds bread; another checks off milk. Both are working on the same list, rather than sending updated copies back and forth. The owner can end access later. That stops further access through Basic, but it can't take back information someone has already downloaded or copied. Sharing should make your data more useful without making you give up control of its home. # External resources Source: https://docs.basic.tech/readings/others/reading Background reading on local-first and user-owned data Curated links for context on **local-first software**, **user-owned data**, and adjacent ecosystems. Basic does not endorse every project below; they are useful for comparison and learning. ## Local-first and sync * [Ink & Switch — local-first](https://www.inkandswitch.com/local-first/) — foundational essay on the pattern. * [localfirstweb.dev](https://localfirstweb.dev/) — community and resources. * [Cambria (Ink & Switch)](https://www.inkandswitch.com/cambria) — schema evolution and JSON interoperability. ## User-owned and decentralized identity * [Solid](https://solidproject.org/) — pods and linked data. * [Autonomous data](https://autonomous-data.noeldemartin.com/introduction.html) — framing for user-held data. * [unhosted.org](https://unhosted.org/) — “no backend” / client-side apps with remote storage. * [0data.app](https://0data.app) — catalog of zero-knowledge / user-owned patterns. * [Fission](http://fission.codes), [remoteStorage](http://remotestorage.io), [Matrix](http://matrix.org) — related stacks and protocols. ## Tools in the local-first space * [Instant](https://www.instantdb.com/), [Electric SQL](https://electric-sql.com/), [PowerSync](https://www.powersync.com/) — sync and realtime databases. * [Obsidian](https://obsidian.md) — local-first notes (product example). # Roadmap Source: https://docs.basic.tech/readings/others/roadmap High-level product direction for Basic. Public direction (not commitments or ETAs): * **SDKs and clients**: Broader first-party support (for example Python, native iOS/Android) beyond React, Next.js, Expo, and REST. * **Collaboration**: Richer real-time and multiplayer-style patterns on top of sync. * **Schema and types**: Stronger TypeScript and codegen from schema definitions. * **History and undo**: Versioning primitives for user-visible undo/redo style flows. * **Sync**: Smarter partial sync so large accounts do not need the full corpus on every device. * **Deployment**: Self-hosted and bring-your-own-infrastructure options for datastores. For what ships when, follow [Twitter / X](https://twitter.com/basic_db) and [Discord](https://discord.gg/PuWD3zhsCk)—this list is intentionally high level. # The PDS Source: https://docs.basic.tech/readings/personal-data-stores A home for your data, independent of the apps you use. Most apps keep your data inside their own systems. Your notes live in one app, your plans in another. Switching tools often means leaving something behind or starting over. Basic starts with a different idea: **your data should belong to you, not to the app you used to create it.** ## A home of your own A **personal data store**, or **PDS**, is the home for your data in Basic. Apps connect to it with your permission to read and save information. Inside your PDS, data is organized into separate spaces. Each space belongs to your account, even when an app creates it. Connecting an app doesn't give it access to everything else. Basic currently hosts your PDS in the cloud. Your devices can keep local copies, while your PDS keeps the shared, up-to-date version as they sync. ## Apps are tools, not containers Think of a notes app as a way to work with your notes, rather than the place that owns them. Disconnecting the app doesn't delete the notes from your PDS. Another compatible app can work with that same data if you allow it. The goal is simple: you should be able to choose better tools without leaving your information behind. # Apps & schemas Source: https://docs.basic.tech/readings/schema Different tools can understand the same data. Your PDS gives data a home. Apps give you ways to use it: write a note, plan a trip, or organize a collection. For an app to work with data, it needs to understand how that data is arranged. That's what a **schema** describes. ## A description that stays with the data A note might have a title and some text. A task might have a name, a due date, and a mark saying whether it's done. A schema describes those parts so apps know what to expect. Basic keeps that description alongside the data in your PDS. It doesn't exist only inside the app that created it. ## More than one way to use your information Two compatible apps could show the same tasks differently: one as a checklist, another as a calendar. With your permission, both can work with the same underlying information. This doesn't mean every app automatically understands every kind of data. Apps still need to support its structure and have your approval to use it. The aim is to let apps offer different experiences without each needing a separate copy of your life. Ready to make something? Head to [Build](/get-started/welcome). # Database (get, add, delete, etc.) Source: https://docs.basic.tech/sdk-reference/expo-rn Learn how to access data using the Expo / RN SDK Use these methods on any table. You'd select a table using `db.from(*insert table name*)`, and chain it with any of the following methods: * `getAll()` * `get(id)` * `add(value)` * `update(id, value)` * `replace(id, value)` * `delete(id)` Make sure not to create any `id` or `created_at` field since these are reserved and exist with every row. You can access them with the `get` operators ### Get all records ```js theme={null} const allNotes = await db.from('notes').getAll(); ``` * **Method:** `getAll()` * **Returns:** Query builder for chaining (see [filtering and ordering](/basic-expo-rn/rn-filtering) for instructions) ### Get by ID ```js theme={null} const note = await db.from('notes').get('note-id-here'); ``` * **Method:** `get(id)` * **Returns:** Record object or throws if not found ### Add a record ```js theme={null} const newNote = await db.from('notes').add({...}); ``` * **Method:** `add(value)` * **Returns:** The newly added record ### Update a record ```js theme={null} const updated = await db.from('notes').update('noteId', { completed: true }) ``` * **Method:** `update(id, value)` ### Replace a record ```js theme={null} const replaced = await db.from('notes').replace('noteId', {...}) ``` * **Method:** `replace(id, value)` ### Delete a record ```js theme={null} const deleted = await db.from('notes').delete('noteId') ``` * **Method:** `delete(id)` # Components (BasicProvider) Source: https://docs.basic.tech/sdk-reference/react-components # BasicProvider To enable the features of the Basic SDK, you need to wrap your app in a `` component. ```tsx main.tsx theme={null} import { BasicProvider } from '@basictech/react' import schema from './basic.config' function App() { return ( ) } ``` ## Props ### schema The Basic schema object containing your project configuration and table definitions. This should be imported from your `basic.config.ts` file. The `project_id` is extracted from `schema.project_id`. ### project\_id The project ID of your Basic project. **Deprecated**: Project ID is now extracted from `schema.project_id`. This prop is kept for backward compatibility but can be omitted if your schema includes the project\_id. ### debug Enable debug mode for additional logging to the console. Useful during development. ```tsx theme={null} ``` ### dbMode Determines which database implementation is used: * **`'sync'`** (default): Uses IndexedDB + WebSocket for local-first sync. Works offline, provides real-time updates, and `useQuery` auto-refreshes. * **`'remote'`**: Uses REST API calls directly to the server. No local storage, requires authentication for all operations, but has no IndexedDB dependencies. ```tsx theme={null} // Use remote mode for SSR-heavy apps or when you don't need offline support ``` See [Database Modes](/basic-nextjs/nextjs-modes) for detailed comparison. ### storage Custom storage adapter for persisting auth tokens. By default, uses `LocalStorageAdapter` which stores data in `localStorage`. Implement the `BasicStorage` interface to use a custom storage solution (e.g., for React Native with AsyncStorage): ```typescript theme={null} interface BasicStorage { get(key: string): Promise set(key: string, value: string): Promise remove(key: string): Promise } ``` ```tsx theme={null} import { BasicProvider, BasicStorage } from '@basictech/react' import AsyncStorage from '@react-native-async-storage/async-storage' const asyncStorageAdapter: BasicStorage = { get: (key) => AsyncStorage.getItem(key), set: (key, value) => AsyncStorage.setItem(key, value), remove: (key) => AsyncStorage.removeItem(key) } ``` ### auth Custom authentication configuration. Most apps omit this and use defaults. ```typescript theme={null} type AuthConfig = { scopes?: string | string[] // default: 'profile,email,app:admin' pds_url?: string // PDS / auth host (default: 'https://pds.basic.id') admin_url?: string // Admin API for telemetry (default: 'https://api.basic.tech') ws_url?: string // Sync WebSocket (default: 'wss://pds.basic.id/ws') /** @deprecated */ server_url?: string // use pds_url instead } ``` ```tsx theme={null} ``` ### devToolbar When `true`, shows the lazy-loaded **Basic dev toolbar** (schema status, useful shortcuts) in environments where it is allowed (for example localhost, `NODE_ENV=development`, or when `debug` is enabled on the provider). Export `BasicDevToolbar` from `@basictech/react` if you want to place it yourself. *** ## Full Example ```tsx main.tsx theme={null} import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { BasicProvider } from '@basictech/react' import { schema } from './basic.config' import App from './App' createRoot(document.getElementById('root')!).render( ) ``` # Database (get, add, update, delete, etc.) Source: https://docs.basic.tech/sdk-reference/react-db # Overview There are 7 methods you can use to interact with the database. They are used in conjunction with the `db` property from the `useBasic()` hook, and are appended in the format of `db.collection('tablename').METHOD()`. ## Methods ### .get() Fetch a single item from the table by its ID. Returns `null` if not found. ### .getAll() Fetch all items from the table. Returns an empty array if no items exist. In the **React / Next.js client SDK** (sync or remote mode), `.getAll()` loads items for that collection and `.filter()` applies your predicate **on the client** after fetch. For **server-side query parameters** on HTTP, use the [REST API filtering guide](/basic-restapi/api-filtering) against the PDS endpoints instead. ### .add() Adds a new item to the table. The `id` is automatically generated by the server. Returns the created object with its new ID. ### .put() Upsert (insert or replace) an item. The `data` object **must include an `id` field**. If an item with that ID exists, it will be replaced entirely. If not, a new item will be created. Returns the upserted object. ### .update() Partially update an existing item by ID. Only the fields you provide will be updated—other fields remain unchanged. Returns the updated object, or `null` if the item was not found. ### .delete() Deletes an item from the table by ID. Returns `true` if deleted, `false` if the item was not found. ### .filter() Filter records using a predicate function. This fetches all records and filters client-side. Returns an array of matching objects. *** # Examples ### Read items There are 2 ways to read items from the database: * `.get('ID_OF_ITEM')`: Fetches a single item by ID * `.getAll()`: Fetches all items from the table ```tsx App.tsx theme={null} import { useBasic, useQuery } from '@basictech/react' import { useState } from 'react' function App() { // Import db from useBasic() inside your React component const { db } = useBasic() // Example 1: Use useQuery to "subscribe" to data from the database // This will automatically re-render when data changes const items = useQuery(() => db.collection('tablename').getAll()) // Example 2: Or get a single item by ID using async/await const [singleItem, setSingleItem] = useState(null) const getItem = async () => { const item = await db.collection('tablename').get('ID_OF_ITEM') if (item) { setSingleItem(item) console.log("Successfully fetched item: ", item) } else { console.log("Item not found") } } return ( <> {/* render items */} ) } ``` ### Add new item ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { db } = useBasic() // Use .add() to add items - id is auto-generated const addItem = async () => { const newItem = await db.collection('tablename').add({ name: 'cutie' }) console.log("Created item with ID: ", newItem.id) } return ( <> ) } ``` ### Put (upsert) item ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { db } = useBasic() // Use .put() to upsert items - requires id in data const upsertItem = async () => { const item = await db.collection('tablename').put({ id: 'specific-id-123', name: 'updated name', completed: true }) console.log("Upserted item: ", item) } return ( <> ) } ``` ### Update item ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { db } = useBasic() // Use .update() to partially update items // Only the fields you provide will be changed const updateItem = async () => { const updated = await db.collection('tablename').update('ID_OF_ITEM', { name: 'super cute' }) if (updated) { console.log("Successfully updated item: ", updated) } else { console.log("Item not found") } } return ( <> ) } ``` ### Delete item ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { db } = useBasic() // Use .delete() to delete items by ID const deleteItem = async () => { const deleted = await db.collection('tablename').delete('ID_OF_ITEM') if (deleted) { console.log("Successfully deleted item") } else { console.log("Item not found") } } return ( <> ) } ``` ### Filter items ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' import { useState } from 'react' function App() { const { db } = useBasic() const [completedItems, setCompletedItems] = useState([]) // Use .filter() to filter items client-side const getCompletedItems = async () => { const items = await db.collection('todos').filter( (item) => item.completed === true ) setCompletedItems(items) console.log("Completed items: ", items) } return ( <> ) } ``` *** # TypeScript Support The `collection` method supports generics for type-safe database operations: ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' // Define your item type type Todo = { id: string title: string completed: boolean createdAt: string } function App() { const { db } = useBasic() // Type-safe collection access const todos = db.collection('todos') const addTodo = async () => { // TypeScript knows the shape of your data const newTodo = await todos.add({ title: 'Learn Basic', completed: false, createdAt: new Date().toISOString() }) console.log(newTodo.id) // TypeScript knows this exists } return } ``` # Hooks (useBasic, useQuery) Source: https://docs.basic.tech/sdk-reference/react-hooks # useBasic The `useBasic` hook is used to access authentication, database, and other Basic features. Any of these properties can be accessed by destructuring the `useBasic` hook. ## Auth State ### isReady Boolean that indicates when auth state has been determined. Use this as a loading state for your app while auth is being verified. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { isReady, isSignedIn } = useBasic() if (!isReady) return
Loading...
return (
{isSignedIn ? "User is signed in" : "User is signed out"}
) } ``` `isAuthReady` is a deprecated alias for `isReady` and will be removed in a future version.
### isSignedIn Boolean that checks if a user is signed in. Only use after `isReady` is true. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { isReady, isSignedIn } = useBasic() return ( <> {isReady ?
{isSignedIn ? "User is signed in" : "User is signed out"}
:
Loading...
} ) } ```
### user The current user object containing the user's ID, email, and name. Returns `null` when not signed in. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { user } = useBasic() console.log("Fetch user ID: ", user?.id) console.log("Fetch user email: ", user?.email) console.log("Fetch user name: ", user?.name) return ( <> {/* render items */} ) } ``` **User type** (fields present depend on IdP / userinfo): ```typescript theme={null} type User = { sub?: string name?: string email?: string picture?: string } ``` ### did The authenticated user's Decentralized Identifier when available; `null` when not signed in or not provided by the identity layer. ### scope Space-separated OAuth scopes granted on the current access token (for example `profile` plus datastore scopes). Use `hasScope` and `missingScopes` for fine-grained checks. ### hasScope Returns whether a given scope string is included on the current token. ### missingScopes Returns scopes your app requested (via provider configuration) that are not yet granted on the current token — useful for prompting re-consent. ## Auth Methods ### signIn Function to sign a user in via the Basic OAuth flow. It will redirect the user to the Basic login page, and then redirect them back to the page they were on. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { signIn } = useBasic() return ( <> ) } ``` `signin` (lowercase) is a deprecated alias for `signIn` and will be removed in a future version. ### signInWithHandle Starts sign-in using a user handle (resolved to a DID server-side). Useful for handle-based login flows in addition to the default `signIn()` redirect. ### signOut Function to sign a user out. User remains on your page after signing out, but `isSignedIn` will be set to false until they sign in again. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { signOut } = useBasic() return ( <> ) } ``` `signout` (lowercase) is a deprecated alias for `signOut` and will be removed in a future version. ### signInWithCode Function to complete authentication using an OAuth authorization code. Useful for custom OAuth flows or React Native apps where you handle the redirect manually. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { signInWithCode } = useBasic() const handleOAuthCallback = async (code: string, state: string) => { const result = await signInWithCode(code, state) if (result.success) { console.log('Signed in successfully!') } else { console.error('Sign in failed:', result.error) } } return <>{/* OAuth callback handling */} } ``` **AuthResult type:** ```typescript theme={null} type AuthResult = { success: boolean error?: string code?: string } ``` ### getToken Fetches the user's JWT access token. The token is automatically refreshed when needed. Pass `{ forceRefresh: true }` to obtain a new token from the server even if the current one is still valid. Use this for authenticated calls to your backend or the Basic APIs. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { getToken } = useBasic() const fetchFromMyAPI = async () => { const token = await getToken() const response = await fetch('/api/my-endpoint', { headers: { 'Authorization': `Bearer ${token}` } }) return response.json() } return ( <> ) } ``` ### getSignInUrl Returns the OAuth sign-in URL without redirecting. Useful for custom sign-in flows, React Native apps, or opening in a popup/modal. ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { getSignInUrl } = useBasic() const handleCustomSignIn = async () => { const url = await getSignInUrl() // Open in popup, new tab, or handle as needed window.open(url, '_blank') } return } ``` `getSignInLink` is a deprecated alias for `getSignInUrl` and will be removed in a future version. ## Development & schema status ### devInfo Snapshot comparing your local `basic.config` schema to the project schema on the server (project id, version, validity, last check). Intended for debugging and the dev toolbar; `null` if no schema is configured on the provider. ### refreshSchemaStatus Re-runs the remote schema check and updates `devInfo`. Used by the dev toolbar and custom diagnostics UIs. ## Database ### db The database object, which you can use to [read, add, update, and delete items](/sdk-reference/react-db). ### dbStatus The current status of the database connection (string enum). Possible values include: * `'LOADING'` — Initializing * `'CONNECTING'` — Connecting to the sync server * `'ONLINE'` — Connected and syncing * `'SYNCING'` — Actively syncing * `'OFFLINE'` — No network (local data may still be available in sync mode) * `'ERROR_WILL_RETRY'` — Non-fatal error; will retry * `'ERROR_TOKEN_EXPIRED'` — Auth token expired for sync; refresh or sign in again * `'ERROR'` — Fatal error ```tsx App.tsx theme={null} import { useBasic } from '@basictech/react' function App() { const { dbStatus } = useBasic() return (
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.