TypeScript SDK for Posif applications with automatic query parameter parsing
A TypeScript SDK for Posif applications with automatic query parameter parsing, designed for Next.js apps launched within Flutter WebViews.
- š Automatic URL query parameter parsing on initialization
- š·ļø TypeScript support with full type definitions
- š Browser and server-side rendering (SSR) compatible
- š¦ ESM and CommonJS module formats
- šÆ Singleton pattern for easy use across your application
- š ļø Utility functions for URL manipulation
- š Authentication token handling
- š Complete API integration for users, groups, and networks
- šļø Token-based MongoDB database connection caching
- š Isolated database access per JWT token
- ā” Advanced cache management and configuration
- š ļø Development mode with local MongoDB URI fallback
- š Optimized for Next.js 15+ App Router
- š¦ NEW: S3-compatible Object Storage with presigned URLs
- š NEW: Automatic S3 key generation with user/group isolation
``bash`
npm install @posif/sdkor
yarn add @posif/sdkor
pnpm add @posif/sdk
The SDK provides three entry points for optimal bundle size and tree-shaking:
`typescript`
// Import everything (backward compatible)
import { getDb, uploadFile, generateUploadUrl } from '@posif/sdk';
Use when: You need both database and storage functionality.
`typescript`
// Import storage functions for API routes (no MongoDB)
import { uploadFile, generateUploadUrl, generateFileUrls } from '@posif/sdk/storage';
Use when: You need S3 storage functionality in API routes. This avoids pulling in MongoDB dependencies, reducing bundle size.
Includes:
- uploadFile() - Complete file upload with progressgenerateUploadUrl()
- - Get presigned upload URLgenerateFileUrls()
- - Get view/download URLsgenerateBatchUploadUrls()
- - Batch upload URLsgenerateDeleteUrl()
- - Get delete URLgeneratePublicUrl()
- - Get public URL for public bucketsgetStorage()
- - Advanced storage API
- Storage cache management functions
ā ļø Note: These functions are SERVER-ONLY and must be used in API routes, not client components.
`typescript`
// Import only server-side database functions (no S3)
import { getDb, getMongoDbConfig } from '@posif/sdk/server';
Use when: You only need MongoDB functionality in server-side code.
Includes:
- getDb() - MongoDB database accessgetMongoDbConfig()
- - Get database configurationposifSDK
- Database cache management functions
- - SDK configuration
ā
Smaller Bundles: Import only what you need
ā
Better Tree-Shaking: Optimized for modern bundlers
ā
Backward Compatible: Main export still works
ā
Zero Breaking Changes: Existing code continues to work
Existing code works without changes! But you can optimize by updating imports:
`typescript
// Before (still works)
import { uploadFile, getDb } from '@posif/sdk';
// After (optimized for storage-only API routes)
import { uploadFile } from '@posif/sdk/storage';
// After (optimized for database-only API routes)
import { getDb } from '@posif/sdk/server';
`
When to migrate:
- API Routes (Storage Only): Use /storage to avoid MongoDB in storage API route bundles/server
- API Routes (Database Only): Use if you only need database access/storage
- Mixed Usage: Keep using main export or use both and /server
`typescript
import sdk from '@posif/sdk';
// The SDK automatically parses URL parameters on load
const params = sdk.getParams();
console.log(params);
// Output: { token: "abc123", user: "john", group: "dev", ... }
// Get specific parameters
const token = sdk.getParam('token');
const user = sdk.getParam('user');
// Check authentication status
if (sdk.isAuthenticated()) {
console.log('User is authenticated');
}
`
`typescript
import sdk from '@posif/sdk';
// Fetch user information
const userInfo = await sdk.getUserInfo();
console.log(userInfo);
// Fetch group information
const groupInfo = await sdk.getGroupInfo();
console.log(groupInfo);
// Fetch group members
const members = await sdk.getMembers(); // Default: limit=20, offset=0
console.log(members);
// Fetch group members with pagination
const paginatedMembers = await sdk.getMembers(10, 5); // limit=10, offset=5
console.log(paginatedMembers);
// Fetch groups that user is part of
const groups = await sdk.getGroups(); // Default: limit=20, offset=0
console.log(groups);
// Fetch groups with role filter
const ownerGroups = await sdk.getGroups(20, 0, 'owner'); // Only groups where user is owner
console.log(ownerGroups);
// Fetch networks that user is part of
const networks = await sdk.getNetworks(); // Default: limit=20, offset=0
console.log(networks);
// Fetch networks with role filter
const ownerNetworks = await sdk.getNetworks(20, 0, 'owner'); // Only networks where user is owner
console.log(ownerNetworks);
`
The SDK now includes a powerful getDb() function optimized for Next.js 15+ App Router with secure MongoDB access:
`typescript
import { NextRequest, NextResponse } from 'next/server';
import { getDb } from '@posif/sdk';
// Next.js 15+ App Router - Automatic token extraction from URL
// Example: /api/users?token=your_jwt_token
export async function GET(req: NextRequest) {
try {
// Get database instance (token automatically extracted from URL parameters)
const db = await getDb(req);
// Use MongoDB native driver
const users = await db.collection('users').find({}).toArray();
return NextResponse.json({ users });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
`
Token Extraction Order:
1. Explicit token parameter: getDb(req, 'your_token')/api/users?token=xxx
2. URL parameters (server-side): (automatically extracted)req.query.token
3. Pages Router query: (Next.js Pages Router)
4. SDK params (browser-only): Fallback for client-side rendering
With explicit JWT token from headers:
`typescript`
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// Extract token from Authorization header
const token = req.headers.authorization?.replace('Bearer ', '');
// Pass token explicitly (overrides URL parameter token)
const db = await getDb(req, token);
const users = await db.collection('users').find({}).toArray();
res.status(200).json({ users });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
For local development, the SDK automatically detects development mode and uses your local MongoDB URI:
`typescript
// .env.local
MONGODB_URI=mongodb://localhost:27017/your-database-name
// In your Next.js API route
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// SDK automatically detects development mode
// Uses MONGODB_URI from .env.local instead of API calls
const db = await getDb(req);
const data = await db.collection('users').find({}).toArray();
res.json({ data });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
`
Development Mode Features:
- ā
Automatically detects NODE_ENV=developmentMONGODB_URI
- ā
Uses from environment variables
- ā
Skips API calls for MongoDB URI
- ā
Perfect for local development and testing
- ā
No need for JWT tokens in development
Check Development Mode:
`typescript
import { getDevelopmentInfo } from '@posif/sdk';
const devInfo = getDevelopmentInfo();
console.log('Development mode:', devInfo.isDevelopment);
console.log('Using local MongoDB:', devInfo.usingLocalMongoDb);
`
Key Features:
- ā
NEW: Token-based database connection caching
- ā
NEW: Isolated database access per JWT token
- ā
NEW: Advanced cache management and configuration
- ā
JWT token authentication with automatic extraction from URL parameters
- ā
Supports explicit token override from Authorization headers
- ā
Fetches MongoDB URI from Posif API with authentication
- ā
Connection pooling and reuse (no reconnection overhead)
- ā
Server-only (throws error if used in client code)
- ā
Full TypeScript support with native MongoDB driver types
The SDK automatically parses the following query parameters from the URL:
| Parameter | Type | Description |
|-----------|------|-------------|
| token | string | Authentication token |user
| | string | User identifier |group
| | string | Group identifier |network
| | string | Network identifier |mode
| | string | Application mode |color
| | string | Theme color |lang
| | string | Language code |
#### getParams(): QueryParams
Returns all parsed query parameters as an object.
#### getParam
Returns the value of a specific parameter.
#### setParams(params: Partial
Updates the stored parameters (useful for testing or manual override).
#### getUserInfo(): Promise/api/v1/users/{id}
Fetches user information from the API endpoint .GET {apiBaseUrl}/api/v1/users/{user_id}
- Endpoint: Accept: application/json
- Headers: , Authorization: Bearer (if token provided)
- Returns: User information object
#### getGroupInfo(): Promise/api/v1/groups/{id}
Fetches group information from the API endpoint .GET {apiBaseUrl}/api/v1/groups/{group_id}
- Endpoint: Accept: application/json
- Headers: , Authorization: Bearer (optional for some groups)
- Returns: Group information object
#### getMembers(limit?: number, offset?: number): Promise/api/v1/groups/{id}/members
Fetches group members from the API endpoint .GET {apiBaseUrl}/api/v1/groups/{group_id}/members
- Endpoint: limit
- Parameters: (default: 20), offset (default: 0)Accept: application/json
- Headers: , Authorization: Bearer (required)
- Returns: Group members with pagination information
#### getGroups(limit?: number, offset?: number, roleType?: 'owner' | 'staff' | 'admin' | 'member'): Promise/api/v1/groups
Fetches groups that the user is part of from the API endpoint .GET {apiBaseUrl}/api/v1/groups
- Endpoint: limit
- Parameters: (default: 20), offset (default: 0), roleType (optional role filter)Accept: application/json
- Headers: , Authorization: Bearer (required)
- Returns: User groups with pagination information and platform filtering status
#### getNetworks(limit?: number, offset?: number, roleType?: 'owner' | 'staff' | 'admin' | 'member'): Promise/api/v1/networks
Fetches networks that the user is part of from the API endpoint .GET {apiBaseUrl}/api/v1/networks
- Endpoint: limit
- Parameters: (default: 20), offset (default: 0), roleType (optional role filter)Accept: application/json
- Headers: , Authorization: Bearer (required)
- Returns: User networks with pagination information and platform filtering status
#### isAuthenticated(): boolean
Checks if a valid authentication token is present.
#### getToken(): string | undefined
Returns the authentication token if present.
#### isBrowser(): boolean
Checks if the SDK is running in a browser environment.
#### refresh(): void
Re-parses parameters from the current URL.
#### clear(): void
Clears all stored parameters.
#### parseQueryParams(url?: string): QueryParams
Parses query parameters from a URL string or current browser location.
#### buildQueryString(params: QueryParams): string
Builds a query string from a parameters object.
#### mergeParams(base: QueryParams, override: Partial
Merges two parameter objects.
#### validateRequiredParams(params: QueryParams, required: (keyof QueryParams)[]): boolean
Validates that required parameters are present.
typescript
interface QueryParams {
token?: string;
user?: string;
group?: string;
network?: string;
mode?: string;
color?: string;
lang?: string;
}
`$3
`typescript
interface UserInfo {
id: string;
name: string;
email: string;
avatar?: string;
role?: string;
permissions?: string[];
}
`$3
`typescript
interface GroupInfo {
id: string;
name: string;
userName: string;
description: string;
memberCount: number;
type: 'public' | 'private';
roleType: string;
roleName: string;
avatar: string;
}
`$3
`typescript
interface Group {
id: string;
user_name: string;
group_name: string;
member_count: string;
avatar: string;
cover_photo: string;
role_id: string;
role_name: string;
role_type: 'owner' | 'staff' | 'admin' | 'member';
}
`$3
`typescript
interface GroupsResponse {
groups: Group[];
pagination: {
limit: number;
offset: number;
count: number;
};
platform_filtered: boolean;
}
`$3
`typescript
interface Network {
id: string;
user_name: string;
network_name: string;
member_count: string;
avatar: string;
cover_photo: string | null;
role_id: string;
role_name: string;
role_type: 'owner' | 'staff' | 'admin' | 'member';
}
`$3
`typescript
interface NetworksResponse {
networks: Network[];
pagination: {
limit: number;
offset: number;
count: number;
};
platform_filtered: boolean;
}
`$3
`typescript
interface NetworkInfo {
id: string;
name: string;
domain: string;
region: string;
status: 'active' | 'inactive' | 'maintenance';
endpoints?: {
api: string;
websocket?: string;
cdn?: string;
};
}
`Next.js Integration
$3
`typescript
// app/layout.tsx
'use client';import { useEffect } from 'react';
import sdk from '@posif/sdk';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
useEffect(() => {
// SDK automatically parses URL parameters
const params = sdk.getParams();
if (params.token) {
// Handle authentication
console.log('User authenticated with token:', params.token);
}
if (params.color) {
// Apply theme color
document.documentElement.style.setProperty('--theme-color', params.color);
}
if (params.lang) {
// Set language
document.documentElement.lang = params.lang;
}
}, []);
return (
{children}
);
}
`$3
`typescript
// pages/_app.tsx
import { useEffect } from 'react';
import type { AppProps } from 'next/app';
import sdk from '@posif/sdk';export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
const params = sdk.getParams();
// Handle URL parameters
if (params.token) {
// Set up authentication
}
if (params.mode) {
// Configure app mode
}
}, []);
return ;
}
`S3 Object Storage
The SDK provides server-side storage functions for S3-compatible object storage with presigned URLs. All storage functions are SERVER-ONLY and must be used in API routes, as they require server-side execution to securely fetch S3 credentials.
$3
Storage functions cannot be called directly from client components. They use
getStorage() internally, which has assertServerOnly() protection.Required Pattern:
1. ā
Create API routes that use storage functions (server-side)
2. ā
Call those API routes from client components
3. ā
Client receives presigned URLs and uploads directly to S3
This will NOT work:
`typescript
'use client';// ā ERROR: This will throw "Function can only be used on the server"
import { uploadFile } from '@posif/sdk/storage';
const key = await uploadFile(file); // ā Throws error in browser
`This is the correct way:
`typescript
// ā
Step 1: API Route (server-side)
// app/api/upload/route.ts
import { generateUploadUrl } from '@posif/sdk';export async function POST(req: NextRequest) {
const { filename } = await req.json();
const { key, uploadUrl } = await generateUploadUrl(filename, req);
return NextResponse.json({ key, uploadUrl });
}
// ā
Step 2: Client Component
'use client';
const response = await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({ filename: file.name })
});
const { key, uploadUrl } = await response.json();
// ā
Step 3: Upload to S3
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
});
`$3
- š Server-Side Security: S3 credentials never exposed to browser
- š Automatic Authentication: JWT token extraction from requests
- š„ User/Group Isolation: Files automatically organized by user and group
- ā” Connection Caching: Optimized credential fetching and reuse
- š Presigned URLs: Secure, temporary URLs for S3 operations
- šŖ Type Safe: Full TypeScript support
$3
`
Client Component (Browser)
ā fetch('/api/upload')
API Route (Server)
ā generateUploadUrl()
SDK ā getStorage() ā Fetch S3 credentials from Posif API
ā
Generate Presigned URL
ā
Return to Client
ā
Client uploads directly to S3 using presigned URL
`$3
Step 1: Create Upload URL API Route (
app/api/upload/route.ts)`typescript
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';export async function POST(req: NextRequest) {
try {
const { filename, contentType, isPrivate } = await req.json();
// Token automatically extracted from request (?token=xxx)
const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
contentType,
isPrivate: isPrivate !== false, // Default to private
});
return NextResponse.json({ key, uploadUrl });
} catch (error: any) {
return NextResponse.json(
{ error: error.message },
{ status: 500 }
);
}
}
`Step 2: Client Component
`typescript
'use client';import { useState } from 'react';
export default function FileUploader() {
const [file, setFile] = useState(null);
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const handleUpload = async () => {
if (!file) return;
setUploading(true);
try {
// Step 1: Get upload URL from API route
const response = await fetch('/api/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
isPrivate: true,
}),
});
const { key, uploadUrl } = await response.json();
// Step 2: Upload directly to S3 with progress tracking
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
setProgress(Math.round((e.loaded / e.total) * 100));
}
});
await new Promise((resolve, reject) => {
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
} else {
reject(new Error(
Upload failed: ${xhr.status}));
}
});
xhr.addEventListener('error', () => reject(new Error('Upload failed')));
xhr.open('PUT', uploadUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
}); alert('Upload successful! Key: ' + key);
} catch (error: any) {
alert('Upload failed: ' + error.message);
} finally {
setUploading(false);
setProgress(0);
}
};
return (
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
disabled={uploading}
/>
);
}
`$3
All these functions must be used in API routes:
####
uploadFile(file, req?, token?, options?)Complete server-side upload (use in API routes that accept FormData).
`typescript
// app/api/upload-direct/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { uploadFile } from '@posif/sdk';export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
}
try {
// Upload file to S3 (all handled server-side)
const key = await uploadFile(file, req, undefined, {
isPrivate: true,
onProgress: (percent) => {
console.log(
Upload progress: ${percent}%);
},
});
return NextResponse.json({ key });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
`Client-side usage:
`typescript
// Upload file via FormData
const formData = new FormData();
formData.append('file', file);const response = await fetch('/api/upload-direct', {
method: 'POST',
body: formData,
});
const { key } = await response.json();
`####
generateUploadUrl(filename, req?, token?, options?)Generate presigned upload URL (use in API routes).
`typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';export async function POST(req: NextRequest) {
const { filename } = await req.json();
// Token auto-extracted from request URL (?token=xxx)
const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
isPrivate: true,
expiresIn: 3600, // 1 hour
});
return NextResponse.json({ key, uploadUrl });
}
`####
generateFileUrls(key, req?, token?, isPrivate?)Generate view and download URLs (use in API routes).
`typescript
// app/api/files/[key]/urls/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateFileUrls } from '@posif/sdk';export async function GET(
req: NextRequest,
{ params }: { params: { key: string } }
) {
const { viewUrl, downloadUrl } = await generateFileUrls(
params.key,
req,
undefined,
true // isPrivate
);
return NextResponse.json({ viewUrl, downloadUrl });
}
`Client-side usage:
`typescript
const response = await fetch(/api/files/${key}/urls);
const { viewUrl, downloadUrl } = await response.json();// Display image

`####
generateDeleteUrl(key, req?, token?, isPrivate?)Generate delete URL (use in API routes).
`typescript
// app/api/files/[key]/delete-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateDeleteUrl } from '@posif/sdk';export async function GET(
req: NextRequest,
{ params }: { params: { key: string } }
) {
const deleteUrl = await generateDeleteUrl(params.key, req);
return NextResponse.json({ deleteUrl });
}
`Client-side usage:
`typescript
const response = await fetch(/api/files/${key}/delete-url);
const { deleteUrl } = await response.json();
await fetch(deleteUrl, { method: 'DELETE' });
`####
generateBatchUploadUrls(files, req?, token?, options?)Generate multiple upload URLs at once (use in API routes).
`typescript
// app/api/upload/batch/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateBatchUploadUrls } from '@posif/sdk';export async function POST(req: NextRequest) {
const { files } = await req.json();
const results = await generateBatchUploadUrls(files, req, undefined, {
isPrivate: true,
});
return NextResponse.json({ results });
}
`Client-side usage:
`typescript
const files = [
{ filename: 'photo1.jpg', contentType: 'image/jpeg' },
{ filename: 'photo2.png', contentType: 'image/png' }
];const response = await fetch('/api/upload/batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ files })
});
const { results } = await response.json();
// results: [{ key, uploadUrl, id }, ...]
`####
generatePublicUrl(key, req?, token?)Get direct public URL for files in public buckets (use in API routes).
`typescript
// app/api/files/[key]/public-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generatePublicUrl } from '@posif/sdk';export async function GET(
req: NextRequest,
{ params }: { params: { key: string } }
) {
const publicUrl = await generatePublicUrl(params.key, req);
return NextResponse.json({ publicUrl });
}
`Benefits of Public URLs:
- ā
No Expiration: URL never expires (unlike presigned URLs)
- ā
No Token Needed: After storing, use URL directly without authentication
- ā
Database Friendly: Perfect for storing in databases
- ā
CDN Compatible: Can be cached by CDNs
Use for: Profile pictures, product images, public content
Don't use for: Private documents, sensitive data
$3
API Routes:
`typescript
// app/api/files/upload-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateUploadUrl } from '@posif/sdk';export async function POST(req: NextRequest) {
const { filename, isPrivate } = await req.json();
const { key, uploadUrl } = await generateUploadUrl(filename, req, undefined, {
isPrivate: isPrivate !== false,
});
return NextResponse.json({ key, uploadUrl });
}
``typescript
// app/api/files/[key]/urls/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateFileUrls } from '@posif/sdk';export async function GET(
req: NextRequest,
{ params }: { params: { key: string } }
) {
const { viewUrl, downloadUrl } = await generateFileUrls(params.key, req);
return NextResponse.json({ viewUrl, downloadUrl });
}
``typescript
// app/api/files/[key]/delete-url/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateDeleteUrl } from '@posif/sdk';export async function GET(
req: NextRequest,
{ params }: { params: { key: string } }
) {
const deleteUrl = await generateDeleteUrl(params.key, req);
return NextResponse.json({ deleteUrl });
}
`Client Component:
`typescript
'use client';import { useState } from 'react';
interface FileItem {
key: string;
filename: string;
viewUrl?: string;
}
export default function FileManager() {
const [files, setFiles] = useState([]);
const handleUpload = async (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (!file) return;
try {
// Step 1: Get upload URL from API
const uploadResponse = await fetch('/api/files/upload-url', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filename: file.name, isPrivate: true }),
});
const { key, uploadUrl } = await uploadResponse.json();
// Step 2: Upload to S3
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
// Step 3: Get view URL
const urlsResponse = await fetch(
/api/files/${key}/urls);
const { viewUrl } = await urlsResponse.json(); // Step 4: Add to list
setFiles((prev) => [...prev, { key, filename: file.name, viewUrl }]);
} catch (error: any) {
alert('Upload failed: ' + error.message);
}
};
const handleDelete = async (key: string) => {
try {
// Get delete URL
const response = await fetch(
/api/files/${key}/delete-url);
const { deleteUrl } = await response.json(); // Delete from S3
await fetch(deleteUrl, { method: 'DELETE' });
// Remove from list
setFiles((prev) => prev.filter((f) => f.key !== key));
} catch (error: any) {
alert('Delete failed: ' + error.message);
}
};
return (
{files.map((file) => (
{file.viewUrl && (

)}
{file.filename}
))}
);
}
`$3
For maximum control in API routes:
`typescript
import { NextRequest, NextResponse } from 'next/server';
import { getStorage } from '@posif/sdk';export async function POST(req: NextRequest) {
// Get storage instance (server-side only)
const storage = await getStorage(req);
// Generate key
const key = await storage.generateKey('photo.jpg', true);
// Generate presigned URL
const uploadUrl = await storage.getPresignedUrl(key, 'upload', true);
// Batch operations
const files = [
{ filename: 'photo1.jpg', contentType: 'image/jpeg' },
{ filename: 'photo2.png', contentType: 'image/png' },
];
const batchResults = await storage.getBatchUploadUrls(files, true);
return NextResponse.json({ key, uploadUrl, batchResults });
}
`$3
Security & Architecture:
1. š Credential Protection: S3 credentials must never be exposed to the browser
2. š Token Validation: Server validates JWT tokens before fetching credentials
3. š„ Access Control: Server enforces user/group file isolation
4. š”ļø Rate Limiting: Server can implement upload limits
5. ā” Performance: Credentials are cached server-side
The storage functions use
assertServerOnly() which throws an error if called from client-side code.$3
ā ļø Important Note about
/storage export:The
/storage export (@posif/sdk/storage) is for bundle optimization in API routes. It excludes MongoDB dependencies to reduce bundle size, but the storage functions still require server-side execution.`typescript
// ā
Correct: Use in API routes to avoid MongoDB in bundle
// app/api/upload/route.ts
import { generateUploadUrl } from '@posif/sdk/storage';export async function POST(req: NextRequest) {
const { key, uploadUrl } = await generateUploadUrl(filename, req);
return NextResponse.json({ key, uploadUrl });
}
``typescript
// ā Incorrect: This will throw error
'use client';
import { uploadFile } from '@posif/sdk/storage';// ā ERROR: "Function can only be used on the server"
const key = await uploadFile(file);
`$3
ā
Secure: S3 credentials never exposed to browser
ā
Simple API Routes: Minimal boilerplate required
ā
Direct S3 Upload: Files go straight to S3 (no proxy through your server)
ā
Progress Tracking: Built-in progress callbacks (via XMLHttpRequest)
ā
Type Safe: Full TypeScript support
ā
User/Group Isolation: Automatic file organization
ā
Flexible: Use high-level functions or
getStorage() for control
ā
Optimized Bundles: Use /storage export to exclude MongoDB from API routes$3
The SDK completely manages (server-side):
- š S3 credential fetching and caching
- š JWT token extraction and validation
- š Presigned URL generation
- š„ User/group file isolation
- ā»ļø Connection pooling and reuse
- ā ļø Error handling and retries
$3
You need to create:
- šļø API Routes: Server endpoints that call SDK functions
- šØ UI Components: Upload buttons, galleries, progress bars
- š¾ Database Logic: Store S3 keys and metadata (optional)
- šÆ Business Logic: Validation, permissions, etc.
For more examples including batch uploads and custom progress bars, see
examples/storage-examples.ts`MIT
Contributions are welcome! Please feel free to submit a Pull Request.