NestJS Integration
This guide explains how to integrate FileZen with NestJS applications using the @filezen/nest package. This package provides a FileZenModule that simplifies configuration and makes the ZenStorage service available for dependency injection throughout your application.
📁 Code Example: See the complete NestJS server example for a working implementation with interactive test interface.
Installation
Install the FileZen NestJS SDK:
Quick Start
1. Environment Configuration
Set your FileZen API key as an environment variable. This can be done in your local .env file during development or as a system environment variable in production.
FILEZEN_API_KEY=your_api_key_hereThe FileZen SDK automatically detects the FILEZEN_API_KEY environment variable for server-side operations, so no additional configuration is needed in your server setup. The keepUploads option defaults to false for server-side usage, so temporary uploads are automatically cleaned up.
2. Import the Module
Import FileZenModule into your root AppModule. The module will automatically detect the FILEZEN_API_KEY from your environment variables.
import { Module } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
FileZenModule.forRoot(),
],
})
export class AppModule {}Default Controller: The FileZen module includes a built-in controller for generating signed URLs at POST /upload/sign. This controller is enabled by default and can be customized or disabled as needed.
3. Inject ZenStorage
You can now inject the ZenStorage service into any of your services or controllers using the @InjectZenStorage() decorator.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadFile(file: Buffer, fileName: string) {
const result = await this.zenStorage.upload(file, {
name: fileName,
});
if (result.error) {
throw result.error;
}
return result.file;
}
}FileZen Functions
Single File Upload
Upload individual files with error handling.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadSingleFile(fileBuffer: Buffer, filename: string, mimeType: string) {
const upload = await this.zenStorage.upload(fileBuffer, {
name: filename,
mimeType: mimeType,
});
if (upload.error) {
throw new Error(`Upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}
}Bulk Upload
Upload multiple files concurrently for better performance.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadMultipleFiles(filesData: Array<{ buffer: Buffer; originalname: string; mimetype: string }>) {
const uploadItems = filesData.map(file => ({
source: file.buffer,
options: {
name: file.originalname,
mimeType: file.mimetype,
},
}));
const uploads = await this.zenStorage.bulkUpload(...uploadItems);
// Check for any upload errors
const failedUploads = uploads.filter(upload => upload.error);
if (failedUploads.length > 0) {
const failures = failedUploads.map(upload => ({
name: upload.name || 'unknown',
error: upload.error?.message || 'Unknown error'
}));
throw new Error(`${failedUploads.length} uploads failed`);
}
return uploads.map(upload => ({
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
}));
}
}Upload from URL
Upload files directly from external URLs.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadFromUrl(sourceUrl: string, fileName: string) {
const upload = await this.zenStorage.uploadFromUrl(sourceUrl, {
name: fileName || 'downloaded-file',
});
if (upload.error) {
throw new Error(`URL upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}
}Upload from Base64
Upload files from base64 encoded data.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadFromBase64(base64Data: string, fileName: string, mimeType: string) {
const upload = await this.zenStorage.uploadFromBase64(base64Data, {
name: fileName || 'base64-file',
mimeType: mimeType,
});
if (upload.error) {
throw new Error(`Base64 upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}
}Manual Multipart Upload Control
For fine-grained control over multipart uploads, especially for large files or when you need to track progress:
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async manualMultipartUpload(file: Buffer, fileName: string, mimeType: string) {
// Start multipart upload session
const { id: sessionId } = await this.zenStorage.multipart.start({
fileName: fileName,
mimeType: mimeType,
totalSize: file.length,
uploadMode: 'STREAMING',
metadata: { type: 'manual_upload' }
});
let uploadedSize = 0;
const chunkSize = 5 * 1024 * 1024; // 5MB chunks
// Upload file in chunks
while (uploadedSize < file.length) {
// Create a chunk from the current position
const endPos = Math.min(file.length, uploadedSize + chunkSize);
const chunk = file.slice(uploadedSize, endPos);
// Upload this specific chunk
await this.zenStorage.multipart.uploadPart({
sessionId: sessionId,
chunk: chunk
});
// Update progress
uploadedSize = endPos;
console.log(`Uploaded ${uploadedSize}/${file.length} bytes`);
}
// Finalize the multipart upload
const result = await this.zenStorage.multipart.finish({
sessionId: sessionId
});
return result;
}
}Generate Signed URL
Generate secure signed URLs for direct uploads from client applications.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
generateUploadUrl(fileKey: string, expirationTime: number = 3600) {
const signedUrl = this.zenStorage.generateSignedUrl({
path: '/files/upload',
fileKey: fileKey,
expiresIn: expirationTime, // 1 hour by default
});
return {
signedUrl: signedUrl,
expiresIn: expirationTime,
};
}
}Delete File
Delete files by their URL.
import { Injectable } from '@nestjs/common';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async deleteFile(fileUrl: string) {
try {
await this.zenStorage.deleteByUrl(fileUrl);
return { success: true, message: 'File deleted successfully' };
} catch (error) {
throw new Error(`Failed to delete file: ${error.message}`);
}
}
}URL Presigning Controller
The FileZen module includes a built-in controller for generating signed URLs for direct file uploads. This is useful for client-side uploads where you want to bypass your server.
Basic Usage
By default, the controller is enabled and available at POST /upload/sign:
// POST /upload/sign
{
"path": "/files/upload",
"fileKey": "my-file.jpg",
"expiresIn": 3600 // optional, defaults to 1 hour
}
// Response
{
"url": "https://api.filezen.dev/files/upload?signature=...&accessKey=...&expires=..."
}Controller Configuration
You can customize the controller behavior when setting up the module:
import { Module } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
FileZenModule.forRoot({
apiKey: 'your-api-key', // Optional: can use FILEZEN_API_KEY env var
controller: {
enabled: true, // Enable/disable the controller (default: true)
path: 'custom/upload/sign', // Custom path (default: 'upload/sign')
}
}),
],
})
export class AppModule {}Authentication
You can add authentication to the controller by providing a middleware function:
import { Module, Injectable } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Injectable()
export class AuthService {
async validateRequest(request: any): Promise<boolean> {
// Your authentication logic here
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) return false;
// Validate token, check user permissions, etc.
return await this.validateToken(token);
}
}
@Module({
imports: [
FileZenModule.forRoot({
apiKey: 'your-api-key', // Optional: can use FILEZEN_API_KEY env var
controller: {
enabled: true,
path: 'upload/sign',
middleware: (request) => {
// You can inject services here if needed
return this.authService.validateRequest(request);
}
}
}),
],
providers: [AuthService],
})
export class AppModule {}Middleware Behavior: The middleware function should return false to reject the request. If the middleware returns false, the controller will throw a BadRequestException with the message “Middleware not passed”. Any other return value (including true, undefined, null, etc.) will allow the request to proceed.
Disable Controller
If you don’t need the presigning functionality, you can disable it:
import { Module } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
FileZenModule.forRoot({
apiKey: 'your-api-key', // Optional: can use FILEZEN_API_KEY env var
controller: {
enabled: false, // Disable the controller
}
}),
],
})
export class AppModule {}Complete NestJS Server Example
Here’s a complete NestJS server implementation with all FileZen endpoints.
Framework Dependencies: This example uses @nestjs/platform-express for file upload handling. Install it with: npm install @nestjs/platform-express. These are just for the example - you can use any file upload handling method with FileZen.
import { Module, Controller, Post, Get, UploadedFile, UploadedFiles, UseInterceptors, Body, Delete } from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { FileZenModule } from '@filezen/nest';
import { ZenStorage } from '@filezen/js';
import { InjectZenStorage } from '@filezen/nest';
// File Service
@Injectable()
export class FileService {
constructor(
@InjectZenStorage()
private readonly zenStorage: ZenStorage
) {}
async uploadSingleFile(file: Express.Multer.File) {
const result = await this.zenStorage.upload(file.buffer, {
name: file.originalname,
mimeType: file.mimetype,
});
if (result.error) {
throw result.error;
}
return result.file;
}
async uploadMultipleFiles(files: Express.Multer.File[]) {
const uploadItems = files.map(file => ({
source: file.buffer,
options: {
name: file.originalname,
mimeType: file.mimetype,
},
}));
const uploads = await this.zenStorage.bulkUpload(...uploadItems);
// Check for any upload errors
const failedUploads = uploads.filter(upload => upload.error);
if (failedUploads.length > 0) {
const failures = failedUploads.map(upload => ({
name: upload.name || 'unknown',
error: upload.error?.message || 'Unknown error'
}));
throw new Error(`${failedUploads.length} uploads failed`);
}
return uploads.map(upload => ({
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
}));
}
async uploadFromUrl(url: string, name?: string) {
const result = await this.zenStorage.uploadFromUrl(url, {
name: name || 'downloaded-file',
});
if (result.error) {
throw result.error;
}
return result.file;
}
async uploadFromBase64(base64Data: string, name?: string, mimeType?: string) {
const result = await this.zenStorage.uploadFromBase64(base64Data, {
name: name || 'base64-file',
mimeType: mimeType,
});
if (result.error) {
throw result.error;
}
return result.file;
}
async deleteFile(url: string) {
await this.zenStorage.deleteByUrl(url);
return { success: true, message: 'File deleted successfully' };
}
}
// Files Controller
@Controller('api/files')
export class FilesController {
constructor(private readonly fileService: FileService) {}
@Get()
getHealth() {
return { message: 'FileZen NestJS Server Running' };
}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new Error('No file provided');
}
return await this.fileService.uploadSingleFile(file);
}
@Post('bulk-upload')
@UseInterceptors(FilesInterceptor('files', 10))
async uploadFiles(@UploadedFiles() files: Express.Multer.File[]) {
if (!files || files.length === 0) {
throw new Error('No files provided');
}
return await this.fileService.uploadMultipleFiles(files);
}
@Post('upload-from-url')
async uploadFromUrl(@Body() body: { url: string; name?: string }) {
if (!body.url) {
throw new Error('URL is required');
}
return await this.fileService.uploadFromUrl(body.url, body.name);
}
@Post('upload-from-base64')
async uploadFromBase64(@Body() body: { base64Data: string; name?: string; mimeType?: string }) {
if (!body.base64Data) {
throw new Error('Base64 data is required');
}
return await this.fileService.uploadFromBase64(body.base64Data, body.name, body.mimeType);
}
@Delete('delete')
async deleteFile(@Body() body: { url: string }) {
if (!body.url) {
throw new Error('File URL is required');
}
return await this.fileService.deleteFile(body.url);
}
}
// App Module
@Module({
imports: [
FileZenModule.forRoot({
controller: {
enabled: true, // Enable the built-in URL presigning controller
path: 'upload/sign',
}
}),
],
controllers: [FilesController],
providers: [FileService],
})
export class AppModule {}Configuration Options
Static Configuration
import { Module } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
FileZenModule.forRoot({
apiKey: 'your-api-key', // Optional: can use FILEZEN_API_KEY env var
baseURL: 'https://api.filezen.com',
keepUploads: false, // Default for Nest.js (server-side)
global: true, // Make the module global
controller: {
enabled: true,
path: 'upload/sign',
middleware: (request) => Promise<boolean>,
}
}),
],
})
export class AppModule {}Async Configuration
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
ConfigModule.forRoot(),
FileZenModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
apiKey: configService.get('FILEZEN_API_KEY'), // Optional: can use FILEZEN_API_KEY env var
baseURL: configService.get('FILEZEN_BASE_URL'),
keepUploads: false, // Default for Nest.js (server-side)
controller: {
enabled: true,
path: 'upload/sign',
middleware: (request) => configService.get('AUTH_ENABLED') ? validateRequest(request) : true,
}
}),
inject: [ConfigService],
global: true,
}),
],
})
export class AppModule {}Server-Side Optimizations
The Nest.js module automatically sets keepUploads: false by default, which is optimized for server-side usage:
- Memory Efficiency: Uploads are not stored in memory after completion
- Stateless Operations: Each upload is independent and doesn’t maintain state
- Better Performance: Reduces memory footprint in long-running server processes
If you need to track uploads (e.g., for progress monitoring), you can explicitly enable it:
import { Module } from '@nestjs/common';
import { FileZenModule } from '@filezen/nest';
@Module({
imports: [
FileZenModule.forRoot({
apiKey: 'your-api-key', // Optional: can use FILEZEN_API_KEY env var
keepUploads: true, // Enable upload tracking
}),
],
})
export class AppModule {}