Server API
Complete API reference for Torrin server-side packages.
Core Service
TorrinService
The core service class that handles upload logic.
import { TorrinService, createInMemoryStore } from '@torrin-kit/server';
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
const service = new TorrinService({
storage: createLocalStorageDriver({ baseDir: './uploads' }),
store: createInMemoryStore(),
defaultChunkSize: 1024 * 1024,
maxChunkSize: 100 * 1024 * 1024,
uploadTtlMs: 24 * 60 * 60 * 1000,
});Constructor Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
storage | TorrinStorageDriver | Yes | - | Storage driver for file operations |
store | TorrinUploadStore | Yes | - | State store for upload sessions |
defaultChunkSize | number | No | 1048576 (1MB) | Suggested chunk size for clients |
maxChunkSize | number | No | 104857600 (100MB) | Maximum allowed chunk size |
uploadTtlMs | number | No | 86400000 (24h) | Time to live for upload sessions |
Methods
initUpload(input): Promise<TorrinUploadSession>
Initializes a new upload session.
const session = await service.initUpload({
fileName: 'video.mp4',
fileSize: 100_000_000,
mimeType: 'video/mp4',
metadata: { userId: '123' },
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | Yes | Original file name |
fileSize | number | Yes | Total file size in bytes |
mimeType | string | No | File MIME type |
metadata | Record<string, any> | No | Custom metadata |
Returns: TorrinUploadSession
handleChunk(input): Promise<void>
Processes an uploaded chunk.
await service.handleChunk({
uploadId: 'u_abc123',
index: 0,
size: 1048576,
stream: readableStream,
hash: 'sha256-hash', // optional
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
uploadId | string | Yes | Upload session ID |
index | number | Yes | Chunk index (0-based) |
size | number | Yes | Chunk size in bytes |
stream | Readable | Yes | Readable stream of chunk data |
hash | string | No | Chunk hash for validation |
Throws: TorrinError if chunk is invalid
getStatus(uploadId): Promise<TorrinUploadStatus>
Retrieves upload status.
const status = await service.getStatus('u_abc123');
console.log(status.receivedChunks); // [0, 1, 2]
console.log(status.missingChunks); // [3, 4, 5, ...]Returns:
interface TorrinUploadStatus {
uploadId: string;
status: UploadStatus;
totalChunks: number;
receivedChunks: number[];
missingChunks: number[];
createdAt: Date;
updatedAt: Date;
expiresAt?: Date;
}completeUpload(uploadId, hash?): Promise<TorrinCompleteResult>
Finalizes the upload.
const result = await service.completeUpload('u_abc123');
console.log('File saved to:', result.location);Returns:
interface TorrinCompleteResult {
uploadId: string;
status: 'completed';
location: TorrinStorageLocation;
metadata?: Record<string, any>;
}Throws: TorrinError with code MISSING_CHUNKS if chunks are missing
abortUpload(uploadId): Promise<void>
Cancels and cleans up an upload.
await service.abortUpload('u_abc123');cleanupExpiredUploads(): Promise<CleanupResult>
Removes uploads past their TTL.
const result = await service.cleanupExpiredUploads();
console.log(`Cleaned ${result.cleaned} uploads`);
console.log(`Errors:`, result.errors);Returns:
interface CleanupResult {
cleaned: number;
errors: string[];
}cleanupStaleUploads(maxAgeMs): Promise<CleanupResult>
Removes uploads not updated within the specified time.
// Clean uploads older than 12 hours
const result = await service.cleanupStaleUploads(12 * 60 * 60 * 1000);Express Integration
createTorrinExpressRouter(options)
Creates an Express router with upload endpoints.
import express from 'express';
import { createTorrinExpressRouter } from '@torrin-kit/server-express';
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
import { createInMemoryStore } from '@torrin-kit/server';
const app = express();
app.use(express.json());
const torrinRouter = createTorrinExpressRouter({
storage: createLocalStorageDriver({ baseDir: './uploads' }),
store: createInMemoryStore(),
defaultChunkSize: 1024 * 1024,
maxChunkSize: 10 * 1024 * 1024,
uploadTtlMs: 24 * 60 * 60 * 1000,
onBeforeInit: async (req, res) => {
// Validate authentication
if (!req.headers.authorization) {
throw new Error('Unauthorized');
}
},
onBeforeChunk: async (req, res) => {
// Rate limiting, logging, etc.
},
onBeforeComplete: async (req, res) => {
// Final validation
},
});
app.use('/api/uploads', torrinRouter);Options
| Option | Type | Required | Description |
|---|---|---|---|
storage | TorrinStorageDriver | Yes | Storage driver instance |
store | TorrinUploadStore | Yes | Upload state store |
defaultChunkSize | number | No | Default chunk size (1MB) |
maxChunkSize | number | No | Maximum chunk size (100MB) |
uploadTtlMs | number | No | Upload TTL (24 hours) |
onBeforeInit | (req, res) => Promise<void> | No | Hook before init |
onBeforeChunk | (req, res) => Promise<void> | No | Hook before chunk |
onBeforeComplete | (req, res) => Promise<void> | No | Hook before complete |
Endpoints Created
| Method | Path | Description |
|---|---|---|
POST | / | Initialize upload |
PUT | /:uploadId/chunks/:index | Upload chunk |
GET | /:uploadId/status | Get upload status |
POST | /:uploadId/complete | Complete upload |
DELETE | /:uploadId | Abort upload |
NestJS Integration
TorrinModule
NestJS module for Torrin integration.
forRoot(options)
Synchronous configuration:
import { Module } from '@nestjs/common';
import { TorrinModule } from '@torrin-kit/server-nestjs';
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
import { createInMemoryStore } from '@torrin-kit/server';
@Module({
imports: [
TorrinModule.forRoot({
storage: createLocalStorageDriver({ baseDir: './uploads' }),
store: createInMemoryStore(),
uploadTtlMs: 24 * 60 * 60 * 1000,
}),
],
})
export class AppModule {}forRootAsync(options)
Asynchronous configuration with dependency injection:
TorrinModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
storage: createS3StorageDriver({
bucket: config.get('S3_BUCKET'),
region: config.get('AWS_REGION'),
}),
store: createInMemoryStore(),
}),
});TorrinService (Injectable)
Inject the service in your providers:
import { Injectable } from '@nestjs/common';
import { InjectTorrin, TorrinService } from '@torrin-kit/server-nestjs';
@Injectable()
export class UploadService {
constructor(
@InjectTorrin() private readonly torrin: TorrinService
) {}
async getUploadStatus(uploadId: string) {
return this.torrin.getStatus(uploadId);
}
async cleanupExpiredUploads() {
return this.torrin.cleanupExpiredUploads();
}
}Storage Drivers
Local Storage
createLocalStorageDriver(config)
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
const storage = createLocalStorageDriver({
baseDir: './uploads', // Final destination
tempDir: './uploads/.temp', // Temp chunks (optional)
preserveFileName: false, // Use uploadId as filename
});Options:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
baseDir | string | Yes | - | Directory for finalized files |
tempDir | string | No | ${baseDir}/.temp | Directory for temp chunks |
preserveFileName | boolean | No | false | Keep original filename |
S3 Storage
createS3StorageDriver(config)
import { createS3StorageDriver } from '@torrin-kit/storage-s3';
const storage = createS3StorageDriver({
bucket: 'my-uploads',
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
keyPrefix: 'uploads/',
});Options:
| Option | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | S3 bucket name |
region | string | Yes | AWS region |
credentials | object | No | AWS credentials (uses default chain if omitted) |
endpoint | string | No | Custom endpoint (for MinIO, R2, etc.) |
forcePathStyle | boolean | No | Use path-style URLs (required for MinIO) |
keyPrefix | string | No | Prefix for all object keys |
getObjectKey | function | No | Custom key generation function |
Upload Store
createInMemoryStore()
In-memory upload state store (for development):
import { createInMemoryStore } from '@torrin-kit/server';
const store = createInMemoryStore();Note: State is lost on server restart. Use Redis or database store for production.
Custom Store Interface
Implement TorrinUploadStore for custom persistence:
interface TorrinUploadStore {
createSession(
init: TorrinSessionInitInput,
chunkSize: number,
ttlMs?: number
): Promise<TorrinUploadSession>;
getSession(uploadId: string): Promise<TorrinUploadSession | null>;
updateSession(
uploadId: string,
patch: Partial<TorrinUploadSession>
): Promise<TorrinUploadSession>;
markChunkReceived(uploadId: string, chunkIndex: number): Promise<void>;
listReceivedChunks(uploadId: string): Promise<number[]>;
deleteSession(uploadId: string): Promise<void>;
// Optional (for cleanup)
listExpiredSessions?(): Promise<TorrinUploadSession[]>;
listAllSessions?(): Promise<TorrinUploadSession[]>;
}Types
TorrinUploadSession
interface TorrinUploadSession {
uploadId: string;
fileName: string;
fileSize: number;
mimeType?: string;
chunkSize: number;
totalChunks: number;
status: UploadStatus;
metadata?: Record<string, any>;
createdAt: Date;
updatedAt: Date;
expiresAt?: Date;
}
type UploadStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';TorrinStorageLocation
interface TorrinStorageLocation {
type: string; // 'local', 's3', 'gcs', etc.
path?: string; // For local storage
bucket?: string; // For S3/GCS
key?: string; // For S3/GCS
url?: string; // Public URL if available
[key: string]: any; // Custom fields
}TorrinStorageDriver
Custom storage driver interface:
interface TorrinStorageDriver {
initUpload(session: TorrinUploadSession): Promise<void>;
writeChunk(
session: TorrinUploadSession,
chunkIndex: number,
stream: Readable,
expectedSize: number,
hash?: string
): Promise<void>;
finalizeUpload(session: TorrinUploadSession): Promise<TorrinStorageLocation>;
abortUpload(session: TorrinUploadSession): Promise<void>;
}Error Handling
All server methods throw TorrinError on failure:
import { TorrinError } from '@torrin-kit/core';
try {
await service.completeUpload(uploadId);
} catch (error) {
if (error instanceof TorrinError) {
console.log('Error code:', error.code);
console.log('Status code:', error.statusCode);
console.log('Details:', error.details);
}
}Common Error Codes
| Code | HTTP | Description |
|---|---|---|
UPLOAD_NOT_FOUND | 404 | Upload session not found |
UPLOAD_ALREADY_COMPLETED | 409 | Upload already finalized |
CHUNK_OUT_OF_RANGE | 400 | Invalid chunk index |
CHUNK_SIZE_MISMATCH | 400 | Chunk size mismatch |
MISSING_CHUNKS | 400 | Cannot complete, missing chunks |
STORAGE_ERROR | 500 | Storage operation failed |
Examples
Basic Server Setup
import express from 'express';
import { createTorrinExpressRouter } from '@torrin-kit/server-express';
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
import { createInMemoryStore } from '@torrin-kit/server';
const app = express();
app.use(express.json());
app.use('/api/uploads', createTorrinExpressRouter({
storage: createLocalStorageDriver({ baseDir: './uploads' }),
store: createInMemoryStore(),
}));
app.listen(3000);import { Module } from '@nestjs/common';
import { TorrinModule } from '@torrin-kit/server-nestjs';
import { createLocalStorageDriver } from '@torrin-kit/storage-local';
import { createInMemoryStore } from '@torrin-kit/server';
@Module({
imports: [
TorrinModule.forRoot({
storage: createLocalStorageDriver({ baseDir: './uploads' }),
store: createInMemoryStore(),
}),
],
})
export class AppModule {}With Authentication
createTorrinExpressRouter({
storage,
store,
onBeforeInit: async (req, res) => {
const token = req.headers.authorization;
if (!token) {
throw new TorrinError('Unauthorized', 'INVALID_REQUEST', 401);
}
// Verify token
const user = await verifyToken(token);
req.user = user;
},
});Periodic Cleanup
// Clean up expired uploads every hour
setInterval(async () => {
const result = await service.cleanupExpiredUploads();
console.log(`Cleaned ${result.cleaned} expired uploads`);
}, 60 * 60 * 1000);Next Steps
- Client API - Client-side API reference
- Storage Drivers - Detailed storage driver guide
- Configuration - Server configuration options
- Error Handling - Error handling strategies