Nxdom Framework

Nusantara eXtreme Development Object Model — dokumentasi resmi

Static Files v2.0.0

Serving Static Files via Node.js — Akses file gambar, video, dokumen, dan asset lainnya melalui Node.js server dengan MIME type detection otomatis.

Pengenalan

Node.js server dapat melayani static files dari folder assets/drive/ dengan endpoint yang lebih pendek dan clean. Express akan otomatis mendeteksi MIME type berdasarkan ekstensi file.

✨ Keuntungan:
  • ✅ Endpoint lebih pendek dan clean
  • ✅ MIME type detection otomatis
  • ✅ Support semua file types (images, videos, PDFs, dll)
  • ✅ CORS enabled untuk cross-origin access
  • ✅ Caching headers otomatis
  • ✅ Performance tinggi dengan Express

Endpoint Mapping

File di folder assets/drive/ dapat diakses via endpoint /drive/:

File Path Node.js Endpoint
assets/drive/images/logo.png http://localhost:3000/drive/images/logo.png
assets/drive/2026/03/a1_xxx.png http://localhost:3000/drive/2026/03/a1_xxx.png
assets/drive/documents/report.pdf http://localhost:3000/drive/documents/report.pdf
assets/drive/videos/demo.mp4 http://localhost:3000/drive/videos/demo.mp4

Image Resize (On-the-fly)

Node.js server mendukung image resize otomatis dengan format URL: /drive/{width}x{height}/{path}

✨ Fitur Resize:
  • ✅ Resize on-the-fly (tidak perlu pre-generate)
  • ✅ Format: /drive/{width}x{height}/{path}
  • ✅ High quality dengan Sharp library
  • ✅ Cache 30 hari untuk performa
  • ✅ Max dimensions: 5000x5000
  • ✅ Fit mode: cover (crop to fit)

Contoh Resize

Original Resized Dimensions
/drive/images/logo.png /drive/200x200/images/logo.png 200x200 px
/drive/2026/03/a1_xxx.png /drive/500x300/2026/03/a1_xxx.png 500x300 px
/drive/images/hero.jpg /drive/1920x1080/images/hero.jpg 1920x1080 px
/drive/photos/avatar.png /drive/100x100/photos/avatar.png 100x100 px (thumbnail)

Penggunaan di HTML

<!-- Thumbnail 200x200 --> <img src="http://localhost:3000/drive/200x200/images/logo.png" alt="Logo Thumbnail"> <!-- Medium 500x300 --> <img src="http://localhost:3000/drive/500x300/2026/03/a1_xxx.png" alt="Medium Image"> <!-- Large 1920x1080 --> <img src="http://localhost:3000/drive/1920x1080/images/hero.jpg" alt="Hero Image"> <!-- Original size (no resize) --> <img src="http://localhost:3000/drive/images/logo.png" alt="Original Logo">

Responsive Images dengan Resize

<picture> <!-- Desktop: 1920x1080 --> <source media="(min-width: 1024px)" srcset="http://localhost:3000/drive/1920x1080/images/hero.jpg"> <!-- Tablet: 1024x768 --> <source media="(min-width: 768px)" srcset="http://localhost:3000/drive/1024x768/images/hero.jpg"> <!-- Mobile: 640x480 --> <img src="http://localhost:3000/drive/640x480/images/hero.jpg" alt="Hero Image"> </picture>

Srcset untuk Retina Display

<img src="http://localhost:3000/drive/400x300/images/product.jpg" srcset="http://localhost:3000/drive/400x300/images/product.jpg 1x, http://localhost:3000/drive/800x600/images/product.jpg 2x" alt="Product Image">

Konfigurasi

Static file serving dikonfigurasi di server.js:

const express = require('express'); const path = require('path'); const fs = require('fs'); const sharp = require('sharp'); // Optional untuk image resize // Image resize endpoint: /drive/{width}x{height}/{path} app.get('/drive/:size(\\d+x\\d+)/:path(*)', async (req, res) => { const [width, height] = req.params.size.split('x').map(Number); const imagePath = path.join(__dirname, 'assets', 'drive', req.params.path); // Resize dengan sharp const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'cover', position: 'center' }) .toBuffer(); res.set('Cache-Control', 'public, max-age=2592000'); res.send(resizedImage); }); // Static files - serve from assets/drive as /drive (original) app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive')));

Konfigurasi ini akan:

  • Serve semua file dari assets/drive/
  • Mapping ke endpoint /drive/*
  • Auto-detect MIME type
  • Enable caching headers
  • ✨ Image resize on-the-fly dengan format /drive/{width}x{height}/{path}

Install Sharp (Optional)

Untuk enable image resize, install Sharp library:

# Install sharp npm install sharp # Restart server pm2 restart nxdom-node
💡 Sharp Library:

Sharp adalah library image processing tercepat untuk Node.js, menggunakan libvips untuk performa tinggi. Mendukung JPEG, PNG, WebP, GIF, AVIF, TIFF, dan SVG.

Supported File Types

Category Extensions MIME Type
Images .png, .jpg, .jpeg, .gif, .webp, .svg, .ico image/*
Videos .mp4, .webm, .ogg, .avi, .mov video/*
Audio .mp3, .wav, .ogg, .m4a audio/*
Documents .pdf, .doc, .docx, .xls, .xlsx application/*
Text .txt, .md, .csv text/*
Archives .zip, .rar, .7z, .tar, .gz application/*
Web .html, .css, .js, .json text/html, text/css, etc

Contoh Penggunaan

Menampilkan Gambar di HTML

<!-- Original size --> <img src="http://localhost:3000/drive/images/logo.png" alt="Logo"> <!-- Resized 200x200 --> <img src="http://localhost:3000/drive/200x200/images/logo.png" alt="Logo Thumbnail"> <!-- Resized 500x300 --> <img src="http://localhost:3000/drive/500x300/2026/03/a1_xxx.png" alt="Medium Image"> <!-- Background image dengan resize --> <div style="background-image: url('http://localhost:3000/drive/1920x1080/images/hero.jpg')"> </div>

Fetch dari JavaScript

// Fetch image as blob fetch('http://localhost:3000/drive/images/logo.png') .then(res => res.blob()) .then(blob => { const url = URL.createObjectURL(blob); document.getElementById('myImage').src = url; }); // Fetch JSON data fetch('http://localhost:3000/drive/data/config.json') .then(res => res.json()) .then(data => console.log(data)); // Download file async function downloadFile(path, filename) { const response = await fetch(`http://localhost:3000/drive/${path}`); const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); } downloadFile('documents/report.pdf', 'report.pdf');

Video Player

<video controls width="640" height="360"> <source src="http://localhost:3000/drive/videos/demo.mp4" type="video/mp4"> <source src="http://localhost:3000/drive/videos/demo.webm" type="video/webm"> Your browser doesn't support video tag. </video>

Audio Player

<audio controls> <source src="http://localhost:3000/drive/audio/music.mp3" type="audio/mpeg"> <source src="http://localhost:3000/drive/audio/music.ogg" type="audio/ogg"> Your browser doesn't support audio tag. </audio>

PDF Viewer

<!-- Embed PDF --> <embed src="http://localhost:3000/drive/documents/manual.pdf" type="application/pdf" width="100%" height="600px"> <!-- iFrame PDF --> <iframe src="http://localhost:3000/drive/documents/manual.pdf" width="100%" height="600px"> </iframe>

Perbedaan PHP vs Node.js

Akses via PHP

Browser Request: http://localhost/assets/drive/images/logo.png ↓ PHP Server (Apache/Nginx) ↓ .htaccess / routing ↓ ImagesController.php (jika ada processing) ↓ Serve file atau process image

Karakteristik:

  • ✅ Dapat melakukan image processing (resize, crop, watermark)
  • ✅ Access control & authentication
  • ⚠️ Overhead PHP execution
  • ⚠️ Lebih lambat untuk file besar

Akses via Node.js

Browser Request: http://localhost:3000/drive/images/logo.png ↓ Node.js Server (Express) ↓ express.static middleware ↓ Direct file serve (no processing)

Karakteristik:

  • ✅ Sangat cepat (direct file serve)
  • ✅ Efficient untuk file besar
  • ✅ CORS enabled
  • ✅ Caching headers otomatis
  • ❌ Tidak ada image processing
  • ❌ Tidak ada access control (public)

Kapan Menggunakan?

Scenario Rekomendasi Alasan
Public images/assets ✅ Node.js Lebih cepat, tidak perlu processing
Image dengan resize/crop ⚠️ PHP Perlu image processing
Private files (auth required) ⚠️ PHP Perlu access control
Video streaming ✅ Node.js Efficient untuk file besar
CDN assets ✅ Node.js Fast serving, caching
User uploads (sensitive) ⚠️ PHP Perlu validation & security

Contoh Lengkap

Image Gallery

<div id="gallery"></div> <script> const images = [ 'drive/2026/03/a1_xxx.png', 'drive/2026/03/a2_xxx.png', 'drive/2026/03/a3_xxx.png' ]; const gallery = document.getElementById('gallery'); images.forEach(img => { const imgElement = document.createElement('img'); imgElement.src = `http://localhost:3000/${img}`; imgElement.alt = img; imgElement.style.width = '200px'; imgElement.style.margin = '10px'; gallery.appendChild(imgElement); }); </script>

Lazy Loading Images

<img src="http://localhost:3000/drive/images/placeholder.png" data-src="http://localhost:3000/drive/images/large-image.jpg" loading="lazy" alt="Large Image">

Responsive Images

<picture> <source media="(min-width: 1024px)" srcset="http://localhost:3000/drive/images/hero-large.jpg"> <source media="(min-width: 768px)" srcset="http://localhost:3000/drive/images/hero-medium.jpg"> <img src="http://localhost:3000/drive/images/hero-small.jpg" alt="Hero Image"> </picture>

Advanced Configuration

Resize Options

Customize resize behavior di server.js:

// fit: 'cover' - Crop to fill (default) const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'cover', position: 'center' }) .toBuffer(); // fit: 'contain' - Fit inside (no crop, add padding) const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'contain', background: '#ffffff' }) .toBuffer(); // fit: 'fill' - Stretch to fill (distort) const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'fill' }) .toBuffer(); // fit: 'inside' - Resize to fit inside (maintain aspect) const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'inside' }) .toBuffer();

Quality & Format

// High quality JPEG const resizedImage = await sharp(imagePath) .resize(width, height) .jpeg({ quality: 90 }) .toBuffer(); // WebP format (smaller size) const resizedImage = await sharp(imagePath) .resize(width, height) .webp({ quality: 80 }) .toBuffer(); // PNG with compression const resizedImage = await sharp(imagePath) .resize(width, height) .png({ compressionLevel: 9 }) .toBuffer();

Custom Cache Headers

// Static files dengan custom cache app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive'), { maxAge: '1d', // Cache 1 hari etag: true, // Enable ETag lastModified: true }));

Multiple Static Folders

// Serve multiple folders app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive'))); app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); app.use('/public', express.static(path.join(__dirname, 'public')));

Index File Support

// Serve index.html untuk directory requests app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive'), { index: 'index.html' })); // Request: http://localhost:3000/drive/gallery/ // Serve: assets/drive/gallery/index.html

Disable Directory Listing

// Disable directory listing (default sudah disabled) app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive'), { index: false, // Tidak serve index.html dotfiles: 'deny' // Block .htaccess, .env, dll }));

Security

Restrict File Types

// Middleware untuk filter file types app.use('/drive', (req, res, next) => { const allowedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.pdf', '.mp4']; const ext = path.extname(req.path).toLowerCase(); if (!allowedExtensions.includes(ext)) { return res.status(403).json({ error: 'File type not allowed' }); } next(); }); app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive')));

Rate Limiting

const rateLimit = require('express-rate-limit'); const fileRateLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 menit max: 1000 // max 1000 requests per window }); app.use('/drive', fileRateLimiter);

Authentication (Optional)

// Protect specific folders app.use('/drive/private', (req, res, next) => { const token = req.headers['authorization']; if (!token || !verifyToken(token)) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); app.use('/drive/private', express.static(path.join(__dirname, 'assets', 'drive', 'private')));

Performance Optimization

Compression

const compression = require('compression'); // Enable gzip compression app.use(compression()); // Static files akan di-compress otomatis

Cache Control

// Long cache untuk static assets app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive'), { maxAge: '365d', // Cache 1 tahun immutable: true // File tidak akan berubah }));

CDN Integration

Untuk production, gunakan CDN untuk static files:

const CDN_URL = process.env.CDN_URL || 'http://localhost:3000'; function getAssetUrl(path) { return `${CDN_URL}/drive/${path}`; } // Usage const imageUrl = getAssetUrl('images/logo.png'); // Development: http://localhost:3000/drive/images/logo.png // Production: https://cdn.yourdomain.com/drive/images/logo.png

Troubleshooting

File not found (404)

Cannot GET /drive/images/notfound.png

Solusi:

  1. Check file exists: dir assets\drive\images\notfound.png
  2. Check path case-sensitive (Linux/macOS)
  3. Check file permissions

CORS Error

Access to image at 'http://localhost:3000/drive/...' from origin 'http://localhost' has been blocked by CORS policy

Solusi: CORS sudah enabled di server.js dengan app.use(cors());

Large File Timeout

Untuk file sangat besar (>100MB), tambahkan timeout:

// Increase timeout untuk large files app.use('/drive', (req, res, next) => { req.setTimeout(300000); // 5 minutes res.setTimeout(300000); next(); }); app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive')));

Best Practices

  1. Organize files by type - Gunakan folder structure yang jelas (images/, videos/, documents/)
  2. Use descriptive filenames - Avoid special characters dan spaces
  3. Optimize images - Compress sebelum upload untuk performance
  4. Set proper cache headers - Long cache untuk static assets
  5. Use CDN for production - Offload static files ke CDN
  6. Monitor disk usage - Regular cleanup untuk old files
  7. Backup important files - Regular backup untuk user uploads

Monitoring

File Access Logs

const morgan = require('morgan'); // Log semua static file requests app.use('/drive', morgan('combined')); app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive')));

Custom Logging

// Log file access dengan custom format app.use('/drive', (req, res, next) => { console.log(`[Static] ${req.method} ${req.url} - ${req.ip}`); next(); }); app.use('/drive', express.static(path.join(__dirname, 'assets', 'drive')));

Production Deployment

Nginx Static Files

Untuk production, serve static files langsung dari Nginx (lebih cepat):

server { listen 80; server_name yourdomain.com; # Static files langsung dari Nginx location /drive/ { alias /var/www/nexaui/assets/drive/; expires 365d; add_header Cache-Control "public, immutable"; } # API requests ke Node.js location /api/ { proxy_pass http://localhost:3000/api/; proxy_http_version 1.1; proxy_set_header Host $host; } # Fallback ke Node.js location / { proxy_pass http://localhost:3000; } }

CloudFlare CDN

Setup CloudFlare sebagai CDN:

  1. Point domain ke server IP
  2. Enable CloudFlare proxy (orange cloud)
  3. Set cache rules untuk /drive/*
  4. Enable "Cache Everything" untuk static assets

File Upload (Bonus)

Untuk handle file upload via Node.js, gunakan multer:

npm install multer
const multer = require('multer'); // Configure storage const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, path.join(__dirname, 'assets', 'drive', 'uploads')); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '_' + Math.round(Math.random() * 1E9); cb(null, file.fieldname + '_' + uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 }, // 10MB max fileFilter: (req, file, cb) => { const allowedTypes = ['image/png', 'image/jpeg', 'image/gif']; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Invalid file type')); } } }); // Upload endpoint app.post('/api/upload', upload.single('file'), (req, res) => { if (!req.file) { return res.status(400).json({ error: 'No file uploaded' }); } res.json({ message: 'File uploaded successfully', filename: req.file.filename, url: `http://localhost:3000/drive/uploads/${req.file.filename}` }); });

Testing

Test Image Loading

<!DOCTYPE html> <html> <head> <title>Test Static Files</title> </head> <body> <h1>Test Static Files</h1> <h2>Images</h2> <img src="http://localhost:3000/drive/images/logo.png" width="200"> <img src="http://localhost:3000/drive/2026/03/a1_xxx.png" width="200"> <h2>Video</h2> <video controls width="400"> <source src="http://localhost:3000/drive/videos/demo.mp4" type="video/mp4"> </video> <h2>PDF</h2> <embed src="http://localhost:3000/drive/documents/manual.pdf" width="100%" height="500px"> </body> </html>

Test dengan JavaScript

// Test image load async function testImageLoad() { try { const response = await fetch('http://localhost:3000/drive/images/logo.png'); console.log('Status:', response.status); console.log('Content-Type:', response.headers.get('Content-Type')); console.log('Content-Length:', response.headers.get('Content-Length')); const blob = await response.blob(); console.log('Blob size:', blob.size, 'bytes'); console.log('Blob type:', blob.type); } catch (error) { console.error('Error:', error); } } testImageLoad();

Comparison: PHP vs Node.js

Aspek PHP (ImagesController) Node.js (Sharp)
Speed ⚠️ Slower (GD library) ✅ Faster (libvips/Sharp)
Resize ✅ Query param (?w=500&h=300) ✅ URL path (/500x300/image.png)
Quality ✅ Good (GD library) ✅ Excellent (Sharp/libvips)
Memory ⚠️ High memory usage ✅ Low memory (streaming)
Format Support PNG, JPG, GIF, WebP PNG, JPG, GIF, WebP, AVIF, TIFF
Authentication ✅ Built-in session/auth ⚠️ Custom middleware
CORS ⚠️ Manual headers ✅ Built-in middleware
Caching ✅ ETag + Cache-Control ✅ ETag + Cache-Control

Performance Benchmark

Operation PHP (GD) Node.js (Sharp)
Resize 1920x1080 → 500x300 ~200ms ~50ms
Resize 4000x3000 → 1024x768 ~800ms ~150ms
Memory usage (1 image) ~50MB ~20MB

Advanced Resize Features

Watermark

// Resize dengan watermark app.get('/drive/:size(\\d+x\\d+)/:path(*)', async (req, res) => { const [width, height] = req.params.size.split('x').map(Number); const imagePath = path.join(__dirname, 'assets', 'drive', req.params.path); const watermarkPath = path.join(__dirname, 'assets', 'watermark.png'); const resizedImage = await sharp(imagePath) .resize(width, height, { fit: 'cover' }) .composite([{ input: watermarkPath, gravity: 'southeast' // Bottom right }]) .toBuffer(); res.send(resizedImage); });

Format Conversion

// Auto-convert ke WebP untuk browser yang support app.get('/drive/:size(\\d+x\\d+)/:path(*)', async (req, res) => { const [width, height] = req.params.size.split('x').map(Number); const imagePath = path.join(__dirname, 'assets', 'drive', req.params.path); const acceptsWebP = req.headers.accept?.includes('image/webp'); let image = sharp(imagePath).resize(width, height); if (acceptsWebP) { image = image.webp({ quality: 80 }); res.set('Content-Type', 'image/webp'); } else { res.set('Content-Type', 'image/jpeg'); } const buffer = await image.toBuffer(); res.send(buffer); });

Blur & Effects

// Resize dengan blur effect const resizedImage = await sharp(imagePath) .resize(width, height) .blur(5) // Blur radius .toBuffer(); // Grayscale const resizedImage = await sharp(imagePath) .resize(width, height) .grayscale() .toBuffer(); // Rotate const resizedImage = await sharp(imagePath) .resize(width, height) .rotate(90) .toBuffer();

Example: Complete Image Service

// Static files dengan logging dan security app.use('/drive', // 1. Logging (req, res, next) => { console.log(`[File] ${req.method} ${req.url}`); next(); }, // 2. Security - block dotfiles (req, res, next) => { if (req.path.includes('/.')) { return res.status(403).send('Forbidden'); } next(); }, // 3. Serve files express.static(path.join(__dirname, 'assets', 'drive'), { maxAge: '1d', etag: true, lastModified: true }) );

Quick Reference

URL Format Contoh Hasil
/drive/{path} /drive/images/logo.png Original size
/drive/{w}x{h}/{path} /drive/500x300/images/logo.png Resized 500x300
/drive/{w}x{h}/{path} /drive/200x200/2026/03/a1.png Resized 200x200
/drive/{w}x{h}/{path} /drive/1920x1080/images/hero.jpg Resized 1920x1080

Next Steps

  • Install Sharp: npm install sharp
  • Test resize: http://localhost:3000/drive/500x300/images/logo.png
  • Setup CDN untuk production
  • Implementasi file upload dengan multer
  • Tambahkan watermark untuk protected images
  • Setup backup strategy untuk user uploads
  • Monitor disk usage dan cleanup old files