@srfoster/one-backend-react
Version:
React components and hooks for One Backend authentication and data management
376 lines (291 loc) • 9.14 kB
Markdown
# TODO
* Security: CORS per app id? Distinct auth/JWT per app login. Prevent one frontend from CRUDing another's data
NOTE: This is a WIP, not recommended for use unless you're me...
# One Backend React
React components and hooks for seamless integration with One Backend - a generalized serverless backend for authentication, CRUD operations, and file management.
## Installation
```bash
npm install @srfoster/one-backend-react
```
## Quick Start
### 1. Get Your App ID
Your App ID is a unique identifier for your application in the One Backend system. You can use any string that uniquely identifies your app, such as:
- `my-todo-app`
- `acme-crm`
- `blog-platform`
- `ecommerce-store`
**Important:** Choose a unique App ID as this isolates your app's data from other applications using the same backend. All users, data records, and files will be scoped to your App ID.
### 2. Setup the Provider
Wrap your app with the `AuthProvider`:
```tsx
import React from 'react';
import { AuthProvider } from '@srfoster/one-backend-react';
function App() {
return (
<AuthProvider config={{
appId: 'your-app-id'
}}>
<YourAppContent />
</AuthProvider>
);
}
```
### 2. Use Authentication
```tsx
import React from 'react';
import { useAuth, LoginForm, RegisterForm } from '@srfoster/one-backend-react';
function AuthPage() {
const { user, logout, isAuthenticated } = useAuth();
if (isAuthenticated) {
return (
<div>
<h1>Welcome, {user?.email}!</h1>
<button onClick={logout}>Logout</button>
</div>
);
}
return (
<div>
<h2>Login</h2>
<LoginForm
onSuccess={() => console.log('Logged in!')}
onError={(error) => console.error(error)}
/>
<h2>Register</h2>
<RegisterForm
onSuccess={() => console.log('Registered!')}
onError={(error) => console.error(error)}
/>
</div>
);
}
```
### 3. Manage Data
```tsx
import React from 'react';
import { useData } from '@srfoster/one-backend-react';
function PostsList() {
const {
data: posts,
loading,
error,
createRecord,
updateRecord,
deleteRecord
} = useData('posts');
const handleCreatePost = async () => {
await createRecord({
data: {
title: 'New Post',
content: 'This is a new post',
status: 'published'
}
});
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<button onClick={handleCreatePost}>Create Post</button>
{posts.map(post => (
<div key={post.id}>
<h3>{post.data.title}</h3>
<p>{post.data.content}</p>
<button onClick={() => deleteRecord(post.id)}>Delete</button>
</div>
))}
</div>
);
}
```
#### Batch Delete Records
```tsx
import React, { useState } from 'react';
import { useData } from '@srfoster/one-backend-react';
function PostsManager() {
const {
data: posts,
loading,
error,
deleteRecords
} = useData('posts');
const [selectedPosts, setSelectedPosts] = useState<string[]>([]);
const handleBatchDelete = async () => {
if (selectedPosts.length === 0) return;
const result = await deleteRecords(selectedPosts);
if (result.success) {
console.log(`Deleted ${result.deleted} posts`);
if (result.failed > 0) {
console.log(`Failed to delete ${result.failed} posts`);
}
setSelectedPosts([]); // Clear selection
}
};
const toggleSelection = (postId: string) => {
setSelectedPosts(prev =>
prev.includes(postId)
? prev.filter(id => id !== postId)
: [...prev, postId]
);
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<div>
<button
onClick={handleBatchDelete}
disabled={selectedPosts.length === 0}
>
Delete Selected ({selectedPosts.length})
</button>
</div>
{posts.map(post => (
<div key={post.id}>
<input
type="checkbox"
checked={selectedPosts.includes(post.id)}
onChange={() => toggleSelection(post.id)}
/>
<h3>{post.data.title}</h3>
<p>{post.data.content}</p>
</div>
))}
</div>
);
}
```
### 4. Handle File Uploads
```tsx
import React from 'react';
import { useFileUpload } from '@srfoster/one-backend-react';
function FileUploader() {
const { uploadFile, uploading, error } = useFileUpload();
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const result = await uploadFile(file);
if (result) {
console.log('File uploaded:', result.fileId);
}
};
return (
<div>
<input type="file" onChange={handleFileUpload} disabled={uploading} />
{uploading && <p>Uploading...</p>}
{error && <p>Error: {error}</p>}
</div>
);
}
```
## API Reference
### Components
#### `AuthProvider`
Provides authentication context to your app.
**Props:**
- `config`: Object with `appId`
- `children`: React nodes
#### `LoginForm`
Pre-built login form component.
**Props:**
- `onSuccess?`: Callback when login succeeds
- `onError?`: Callback when login fails
- `className?`: CSS class name
#### `RegisterForm`
Pre-built registration form component.
**Props:**
- `onSuccess?`: Callback when registration succeeds
- `onError?`: Callback when registration fails
- `className?`: CSS class name
- `showNameFields?`: Whether to show first/last name fields (default: true)
### Hooks
#### `useAuth()`
Provides authentication state and methods.
**Returns:**
- `user`: Current user object or null
- `token`: JWT token or null
- `isLoading`: Boolean indicating loading state
- `isAuthenticated`: Boolean indicating if user is logged in
- `login(email, password)`: Login function
- `register(email, password, firstName?, lastName?)`: Registration function
- `logout()`: Logout function
- `client`: OneBackendClient instance
#### `useData(resourceType)`
Manages CRUD operations for a specific resource type.
**Returns:**
- `data`: Array of records
- `loading`: Boolean indicating loading state
- `error`: Error message or null
- `pagination`: Pagination info
- `fetchData(page?, limit?, filters?)`: Fetch data function
- `createRecord(request)`: Create new record
- `updateRecord(id, request)`: Update existing record
- `deleteRecord(id)`: Delete single record
- `deleteRecords(ids)`: Batch delete multiple records
- `ids`: Array of record IDs (max 100)
- Returns: `{ success: boolean, deleted: number, failed: number, details: any[] }`
- `refresh()`: Refresh data
#### `useFileUpload()`
Handles file uploads to S3.
**Returns:**
- `uploadFile(file)`: Upload file function
- `getDownloadUrl(fileId)`: Get download URL
- `deleteFile(fileId)`: Delete file
- `uploading`: Boolean indicating upload state
- `error`: Error message or null
### Client
#### `OneBackendClient`
Direct API client for advanced usage.
```tsx
import { useAuth } from '@srfoster/one-backend-react';
function MyComponent() {
const { client } = useAuth();
// Use client methods directly
const handleCustomRequest = async () => {
const response = await client.getRecords('custom-resource');
// ...
};
}
```
## Configuration
### Environment Setup
You just need:
1. **App ID**: A unique identifier for your application (e.g., `my-app-name`)
The API connection is handled automatically by the package - no configuration needed!
### TypeScript Support
This package is written in TypeScript and provides full type definitions.
---
## For One Backend Maintainers
### Updating the API URL
When the One Backend is deployed to a new AWS region or account, the API Gateway URL will change. To update the React package automatically:
1. **Run the automated update script**:
```bash
cd react-package
npm run update-api-url
```
This script will:
- Fetch the current API URL from your serverless deployment
- Automatically update `src/api/client.ts` with the new endpoint
- Confirm the update was successful
2. **Build and publish the updated package**:
```bash
npm run build
npm version patch # or minor/major as appropriate
npm publish --access public
```
### Manual API URL Update (if needed)
If the automated script fails, you can manually update the API URL:
1. **Get the API URL** from your serverless deployment:
```bash
cd /path/to/one-backend
npx serverless info
```
Look for any endpoint in the output (e.g., `https://abc123xyz.execute-api.us-west-2.amazonaws.com/dev`)
2. **Update the client configuration**:
Edit `src/api/client.ts` and update the hardcoded URL:
```typescript
private static readonly API_URL = 'https://your-new-endpoint.execute-api.region.amazonaws.com/dev';
```
This ensures all React package users automatically connect to the correct backend without needing to configure URLs.
## License
MIT