Easy Azure Blob Storage uploads for Expo and React Native applications
A powerful, type-safe library that simplifies Azure Blob Storage integration with built-in Expo ImagePicker support, progress tracking, and comprehensive error handling.
- π₯ Easy Integration - Simple setup with Azure Blob Storage
- π± Expo ImagePicker Support - Built-in image/camera functionality
- π Progress Tracking - Real-time upload progress monitoring
- π SAS Token Management - Secure token handling and refresh
- π Full TypeScript Support - Complete type definitions
- π Multiple File Uploads - Batch upload with progress tracking
- β‘ Framework Agnostic - Core uploader works with any React Native app
- π‘οΈ Error Handling - Comprehensive error management
- π― Permission Management - Automatic permission requests
- π± Cross-Platform - Works on iOS, Android, and Web
npm install expo-azure-blob-storage
This package requires the following peer dependencies:
npx expo install expo-file-system expo-image-picker
Manual Installation:
npm install expo-file-system expo-image-picker
import { AzureBlobUploader } from 'expo-azure-blob-storage';
const uploader = new AzureBlobUploader({
storageAccount: 'your-storage-account',
containerName: 'your-container',
sasToken: 'your-sas-token'
});
const uploadFile = async (fileUri: string) => {
const result = await uploader.uploadFile(fileUri, 'my-image.jpg', 'image');
if (result.success) {
console.log('β
Upload successful!');
console.log('π File URL:', result.url);
console.log('π File Size:', result.size, 'bytes');
} else {
console.error('β Upload failed:', result.error);
}
};
import { ExpoImageUploader } from 'expo-azure-blob-storage';
const imageUploader = new ExpoImageUploader({
storageAccount: 'your-storage-account',
containerName: 'your-container',
sasToken: 'your-sas-token'
});
const uploadFromLibrary = async () => {
const result = await imageUploader.quickUploadFromLibrary(
'my-photo.jpg',
{ quality: 0.8 },
(progress) => {
const percent = Math.round((progress.totalBytesWritten / progress.totalBytesExpectedToWrite) * 100);
console.log(`Upload progress: ${percent}%`);
}
);
if (result?.success) {
console.log('π Image uploaded:', result.url);
}
};
The core uploader class for Azure Blob Storage operations.
new AzureBlobUploader(config: AzureBlobConfig)
Upload a single file to Azure Blob Storage.
const result = await uploader.uploadFile(
'file:///path/to/file.jpg',
'my-image.jpg',
'image'
);
Upload a file with progress tracking.
const result = await uploader.uploadWithProgress(
fileUri,
'image.jpg',
'image',
(progress) => console.log(`${progress.totalBytesWritten}/${progress.totalBytesExpectedToWrite}`)
);
Upload multiple files with batch progress tracking.
const files = [
{ uri: 'file:///path/1.jpg', name: 'image1.jpg', type: 'image' },
{ uri: 'file:///path/2.jpg', name: 'image2.jpg', type: 'image' }
];
const results = await uploader.uploadMultipleFiles(
files,
(fileIndex, progress) => console.log(`File ${fileIndex + 1} progress:`, progress),
(fileIndex, result) => console.log(`File ${fileIndex + 1} completed:`, result)
);
Expo-specific image handling built on top of AzureBlobUploader.
Pick an image from the library and upload in one step.
const result = await imageUploader.quickUploadFromLibrary(
'profile-pic.jpg',
{ quality: 0.8, allowsEditing: true },
(progress) => console.log('Progress:', progress)
);
Take a photo and upload in one step.
const result = await imageUploader.quickUploadFromCamera(
'camera-photo.jpg',
{ quality: 0.9, aspect: [16, 9] }
);
Pick multiple images from the library.
const pickerResult = await imageUploader.pickMultipleImagesFromLibrary({
selectionLimit: 5,
quality: 0.8
});
interface AzureBlobConfig {
storageAccount: string; // Azure storage account name
containerName: string; // Blob container name
sasToken: string; // SAS token with required permissions
}
interface ImagePickerOptions {
quality?: number; // Image quality (0-1)
aspect?: [number, number]; // Aspect ratio [width, height]
allowsEditing?: boolean; // Allow editing after selection
allowsMultipleSelection?: boolean; // Allow multiple image selection
selectionLimit?: number; // Maximum number of images to select
}
const uploadWithProgressBar = async (fileUri: string) => {
const result = await uploader.uploadWithProgress(
fileUri,
'document.pdf',
'document',
(progress) => {
const percentage = Math.round(
(progress.totalBytesWritten / progress.totalBytesExpectedToWrite) * 100
);
// Update your UI progress bar
setUploadProgress(percentage);
console.log(`π Upload Progress: ${percentage}%`);
}
);
return result;
};
const uploadMultipleWithErrorHandling = async (files: Array<{uri: string, name: string}>) => {
const results = await uploader.uploadMultipleFiles(
files.map(f => ({ ...f, type: 'image' as const })),
(fileIndex, progress) => {
console.log(`File ${fileIndex + 1} progress:`, progress);
},
(fileIndex, result) => {
if (result.success) {
console.log(`β
File ${fileIndex + 1} uploaded successfully`);
} else {
console.error(`β File ${fileIndex + 1} failed:`, result.error);
}
}
);
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`π Upload Summary: ${successful.length} successful, ${failed.length} failed`);
return { successful, failed };
};
import React, { useState } from 'react';
import { View, Button, Text, ProgressBarAndroid } from 'react-native';
import { ExpoImageUploader } from 'expo-azure-blob-storage';
const ImageUploadComponent = () => {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);
const uploader = new ExpoImageUploader({
storageAccount: 'your-account',
containerName: 'your-container',
sasToken: 'your-sas-token'
});
const handleUpload = async () => {
setUploading(true);
setProgress(0);
try {
const result = await uploader.quickUploadFromLibrary(
undefined, // Auto-generate filename
{ quality: 0.8, allowsEditing: true },
(progressData) => {
const percentage = Math.round(
(progressData.totalBytesWritten / progressData.totalBytesExpectedToWrite) * 100
);
setProgress(percentage);
}
);
if (result?.success) {
setUploadedUrl(result.url);
}
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
};
return (
<View>
<Button title="Upload Image" onPress={handleUpload} disabled={uploading} />
{uploading && (
<View>
<Text>Uploading... {progress}%</Text>
<ProgressBarAndroid styleAttr="Horizontal" progress={progress / 100} />
</View>
)}
{uploadedUrl && (
<Text>β
Upload successful! URL: {uploadedUrl}</Text>
)}
</View>
);
};
// β
Good: Fetch SAS tokens from your secure backend
const fetchSasToken = async () => {
const response = await fetch('https://your-api.com/sas-token', {
headers: { 'Authorization': `Bearer ${userToken}` }
});
return response.json();
};
// β Bad: Never hardcode SAS tokens in your app
const badConfig = {
storageAccount: 'account',
containerName: 'container',
sasToken: 'sv=2023-01-03&ss=b&srt=sco&sp=rwdlacx&se=2024-01-01T00:00:00Z&st=2023-01-01T00:00:00Z&spr=https&sig=...'
};
Create SAS tokens with minimal required permissions:
# Minimum permissions for uploads
az storage container generate-sas \
--account-name youraccount \
--name yourcontainer \
--permissions acw \
--start 2024-01-01T00:00:00Z \
--expiry 2024-01-02T00:00:00Z
const handleUploadWithErrorHandling = async (fileUri: string) => {
try {
const result = await uploader.uploadFile(fileUri, 'image.jpg', 'image');
if (!result.success) {
switch (result.error) {
case 'File does not exist':
console.error('π File not found');
break;
case 'File is empty':
console.error('π Empty file');
break;
case 'File size exceeds maximum':
console.error('π File too large');
break;
default:
console.error('β Upload failed:', result.error);
}
}
return result;
} catch (error) {
if (error.message.includes('Network')) {
console.error('π Network error - check internet connection');
} else if (error.message.includes('Permission')) {
console.error('π Permission denied - check SAS token');
} else {
console.error('π₯ Unexpected error:', error);
}
return { success: false, error: error.message };
}
};
1. "SAS token is invalid" Error
// Check token permissions and expiry
const config = uploader.getConfig();
console.log('Storage Account:', config.storageAccount);
console.log('Container:', config.containerName);
console.log('Base URL:', config.baseUrl);
2. "Permission denied" on iOS
// Check and request permissions
const permissions = await imageUploader.checkPermissions();
if (!permissions.mediaLibrary) {
await imageUploader.requestMediaLibraryPermission();
}
3. "File not found" Error
// Verify file exists before upload
const fileInfo = await FileSystem.getInfoAsync(fileUri);
console.log('File exists:', fileInfo.exists);
console.log('File size:', fileInfo.size);
This library is written in TypeScript and includes complete type definitions:
import {
AzureBlobUploader,
ExpoImageUploader,
AzureBlobConfig,
UploadResult,
UploadProgress,
MediaType,
ImagePickerOptions
} from 'expo-azure-blob-storage';
interface UploadResult {
success: boolean;
fileName?: string;
url?: string;
size?: number;
contentType?: string;
error?: string;
}
interface UploadProgress {
totalBytesWritten: number;
totalBytesExpectedToWrite: number;
}
interface AzureBlobConfig {
storageAccount: string;
containerName: string;
sasToken: string;
}
type MediaType = 'image' | 'video' | 'document';
Contributions are welcome! Please read our contributing guidelines for details on our code of conduct and development process.
# Clone the repository
git clone https://github.com/katungi/expo-azure-blob-storage.git
# Install dependencies
npm install
# Run tests
npm test
# Build the library
npm run build
This project is licensed under the MIT License - see the LICENSE file for details.
If you found this library helpful, please consider:
- β Starring the repository
- π Reporting bugs
- π‘ Suggesting new features
- π Improving documentation
Made with β€οΈ by Katungi Dennis