Skip to content

Server API

Complete API reference for Torrin server-side packages.

Core Service

TorrinService

The core service class that handles upload logic.

typescript
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

OptionTypeRequiredDefaultDescription
storageTorrinStorageDriverYes-Storage driver for file operations
storeTorrinUploadStoreYes-State store for upload sessions
defaultChunkSizenumberNo1048576 (1MB)Suggested chunk size for clients
maxChunkSizenumberNo104857600 (100MB)Maximum allowed chunk size
uploadTtlMsnumberNo86400000 (24h)Time to live for upload sessions

Methods

initUpload(input): Promise<TorrinUploadSession>

Initializes a new upload session.

typescript
const session = await service.initUpload({
  fileName: 'video.mp4',
  fileSize: 100_000_000,
  mimeType: 'video/mp4',
  metadata: { userId: '123' },
});

Parameters:

FieldTypeRequiredDescription
fileNamestringYesOriginal file name
fileSizenumberYesTotal file size in bytes
mimeTypestringNoFile MIME type
metadataRecord<string, any>NoCustom metadata

Returns: TorrinUploadSession


handleChunk(input): Promise<void>

Processes an uploaded chunk.

typescript
await service.handleChunk({
  uploadId: 'u_abc123',
  index: 0,
  size: 1048576,
  stream: readableStream,
  hash: 'sha256-hash', // optional
});

Parameters:

FieldTypeRequiredDescription
uploadIdstringYesUpload session ID
indexnumberYesChunk index (0-based)
sizenumberYesChunk size in bytes
streamReadableYesReadable stream of chunk data
hashstringNoChunk hash for validation

Throws: TorrinError if chunk is invalid


getStatus(uploadId): Promise<TorrinUploadStatus>

Retrieves upload status.

typescript
const status = await service.getStatus('u_abc123');
console.log(status.receivedChunks); // [0, 1, 2]
console.log(status.missingChunks);  // [3, 4, 5, ...]

Returns:

typescript
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.

typescript
const result = await service.completeUpload('u_abc123');
console.log('File saved to:', result.location);

Returns:

typescript
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.

typescript
await service.abortUpload('u_abc123');

cleanupExpiredUploads(): Promise<CleanupResult>

Removes uploads past their TTL.

typescript
const result = await service.cleanupExpiredUploads();
console.log(`Cleaned ${result.cleaned} uploads`);
console.log(`Errors:`, result.errors);

Returns:

typescript
interface CleanupResult {
  cleaned: number;
  errors: string[];
}

cleanupStaleUploads(maxAgeMs): Promise<CleanupResult>

Removes uploads not updated within the specified time.

typescript
// 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.

typescript
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

OptionTypeRequiredDescription
storageTorrinStorageDriverYesStorage driver instance
storeTorrinUploadStoreYesUpload state store
defaultChunkSizenumberNoDefault chunk size (1MB)
maxChunkSizenumberNoMaximum chunk size (100MB)
uploadTtlMsnumberNoUpload TTL (24 hours)
onBeforeInit(req, res) => Promise<void>NoHook before init
onBeforeChunk(req, res) => Promise<void>NoHook before chunk
onBeforeComplete(req, res) => Promise<void>NoHook before complete

Endpoints Created

MethodPathDescription
POST/Initialize upload
PUT/:uploadId/chunks/:indexUpload chunk
GET/:uploadId/statusGet upload status
POST/:uploadId/completeComplete upload
DELETE/:uploadIdAbort upload

NestJS Integration

TorrinModule

NestJS module for Torrin integration.

forRoot(options)

Synchronous configuration:

typescript
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:

typescript
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:

typescript
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)

typescript
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:

OptionTypeRequiredDefaultDescription
baseDirstringYes-Directory for finalized files
tempDirstringNo${baseDir}/.tempDirectory for temp chunks
preserveFileNamebooleanNofalseKeep original filename

S3 Storage

createS3StorageDriver(config)

typescript
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:

OptionTypeRequiredDescription
bucketstringYesS3 bucket name
regionstringYesAWS region
credentialsobjectNoAWS credentials (uses default chain if omitted)
endpointstringNoCustom endpoint (for MinIO, R2, etc.)
forcePathStylebooleanNoUse path-style URLs (required for MinIO)
keyPrefixstringNoPrefix for all object keys
getObjectKeyfunctionNoCustom key generation function

Upload Store

createInMemoryStore()

In-memory upload state store (for development):

typescript
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:

typescript
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

typescript
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

typescript
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:

typescript
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:

typescript
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

CodeHTTPDescription
UPLOAD_NOT_FOUND404Upload session not found
UPLOAD_ALREADY_COMPLETED409Upload already finalized
CHUNK_OUT_OF_RANGE400Invalid chunk index
CHUNK_SIZE_MISMATCH400Chunk size mismatch
MISSING_CHUNKS400Cannot complete, missing chunks
STORAGE_ERROR500Storage operation failed

Examples

Basic Server Setup

typescript
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);
typescript
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

typescript
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

typescript
// 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