A sophisticated Node.js web API for file storage with S3-like functionality, MySQL metadata storage, and JWT authentication.
-
File Management
- Upload files (images, documents, archives)
- Chunked uploads for large files
- Download and stream files
- Delete files
- File metadata management
-
User Authentication
- JWT-based authentication
- User registration and login
- Password hashing with Argon2
- Token refresh mechanism
-
Security
- Rate limiting
- CORS protection
- Helmet security headers
- Path traversal prevention
- Input validation and sanitization
-
Storage
- Local file storage with organized directory structure
- MySQL database for metadata
- User storage quotas
- File checksums for integrity
- Runtime: Node.js (v18+)
- Framework: Express.js
- Database: MySQL with Sequelize ORM
- Authentication: JWT (jsonwebtoken)
- Password Hashing: Argon2
- File Upload: Multer
- Validation: express-validator
- Security: Helmet, CORS, express-rate-limit
LocalStorageApi/
├── config/
│ ├── app.js # Application configuration
│ └── database.js # Database configuration
├── middleware/
│ ├── auth.js # Authentication middleware
│ ├── errorHandler.js # Error handling
│ ├── security.js # Security middleware
│ ├── validation.js # Request validation
│ └── index.js # Middleware exports
├── models/
│ ├── User.js # User model
│ ├── File.js # File model
│ ├── ChunkedUpload.js # Chunked upload model
│ └── index.js # Model associations
├── routes/
│ ├── auth.js # Authentication routes
│ ├── files.js # File routes
│ └── index.js # Route exports
├── services/
│ └── uploadService.js # File upload service
├── scripts/
│ ├── syncDatabase.js # Database sync script
│ └── seedDatabase.js # Database seed script
├── uploads/ # File storage directory
├── .env # Environment variables
├── .env.example # Example environment file
├── package.json # Dependencies
├── server.js # Main server file
└── README.md # This file
- Node.js v18 or higher
- MySQL 8.0 or higher
- npm or yarn
-
Clone the repository
cd LocalStorageApi -
Install dependencies
npm install
-
Configure environment
# Copy example env file cp .env.example .env # Edit .env with your settings # Make sure to set: # - DB_PASSWORD (your MySQL password) # - JWT_SECRET (generate a secure secret)
-
Create MySQL database
CREATE DATABASE local_storage_api;
-
Sync database tables
npm run db:sync
-
Seed sample data (optional)
npm run db:seed
-
Start the server
# Development mode (with auto-reload) npm run dev # Production mode npm start
http://localhost:3001/api
All protected endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <your_jwt_token>
POST /api/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"username": "johndoe",
"password": "SecurePass@123",
"confirmPassword": "SecurePass@123",
"firstName": "John",
"lastName": "Doe"
}Response:
{
"success": true,
"message": "Registration successful",
"data": {
"user": {
"id": "uuid",
"email": "user@example.com",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"role": "user",
"storageUsed": 0,
"storageLimit": 5368709120
},
"tokens": {
"accessToken": "jwt_token",
"refreshToken": "refresh_token",
"expiresIn": "7d"
}
}
}POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass@123"
}GET /api/auth/me
Authorization: Bearer <token>PUT /api/auth/me
Authorization: Bearer <token>
Content-Type: application/json
{
"firstName": "John",
"lastName": "Smith",
"username": "johnsmith"
}POST /api/auth/change-password
Authorization: Bearer <token>
Content-Type: application/json
{
"currentPassword": "OldPass@123",
"newPassword": "NewPass@456",
"confirmNewPassword": "NewPass@456"
}POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "your_refresh_token"
}POST /api/auth/logout
Authorization: Bearer <token>POST /api/files/upload
Authorization: Bearer <token>
Content-Type: multipart/form-data
file: <binary>
description: "My file description"
tags: ["tag1", "tag2"]
isPublic: falseResponse:
{
"success": true,
"message": "File uploaded successfully",
"data": {
"file": {
"id": "uuid",
"originalName": "document.pdf",
"mimeType": "application/pdf",
"size": 1024000,
"formattedSize": "1.00 MB",
"url": "http://localhost:3001/api/files/uuid",
"downloadUrl": "http://localhost:3001/api/files/uuid/download",
"isPublic": false,
"description": "My file description",
"tags": ["tag1", "tag2"],
"uploadedAt": "2024-01-01T00:00:00.000Z"
}
}
}GET /api/files?page=1&limit=20&sortBy=created_at&sortOrder=desc&search=document
Authorization: Bearer <token>Query Parameters:
page- Page number (default: 1)limit- Items per page (default: 20, max: 100)sortBy- Sort field: created_at, original_name, size, mime_typesortOrder- Sort direction: asc, descmimeType- Filter by MIME type prefix (e.g., "image/")search- Search in filename
GET /api/files/:fileId
Authorization: Bearer <token>GET /api/files/:fileId/download
Authorization: Bearer <token>GET /api/files/:fileId/view
Authorization: Bearer <token>PUT /api/files/:fileId
Authorization: Bearer <token>
Content-Type: application/json
{
"description": "Updated description",
"tags": ["new-tag"],
"isPublic": true
}DELETE /api/files/:fileId
Authorization: Bearer <token>GET /api/files/stats/summary
Authorization: Bearer <token>For large files, use chunked uploads:
POST /api/files/chunked/init
Authorization: Bearer <token>
Content-Type: application/json
{
"fileName": "large-video.mp4",
"fileSize": 1073741824,
"mimeType": "video/mp4",
"totalChunks": 205
}POST /api/files/chunked/:uploadId/chunk
Authorization: Bearer <token>
Content-Type: multipart/form-data
X-Chunk-Number: 0
chunk: <binary>POST /api/files/chunked/:uploadId/complete
Authorization: Bearer <token>
Content-Type: application/json
{
"description": "My large video",
"tags": ["video"],
"isPublic": false
}GET /api/files/chunked/:uploadId/status
Authorization: Bearer <token>DELETE /api/files/chunked/:uploadId
Authorization: Bearer <token>| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Environment (development/production) | development |
PORT |
Server port | 3000 |
HOST |
Server host | localhost |
DB_HOST |
MySQL host | localhost |
DB_PORT |
MySQL port | 3306 |
DB_NAME |
Database name | local_storage_api |
DB_USER |
Database user | root |
DB_PASSWORD |
Database password | - |
JWT_SECRET |
JWT signing secret | - |
JWT_EXPIRES_IN |
Access token expiry | 7d |
UPLOAD_DIR |
Upload directory | ./uploads |
MAX_FILE_SIZE |
Max file size (bytes) | 104857600 (100MB) |
ALLOWED_FILE_TYPES |
Allowed MIME types | image/*,application/pdf,... |
RATE_LIMIT_WINDOW_MS |
Rate limit window | 900000 (15min) |
RATE_LIMIT_MAX_REQUESTS |
Max requests per window | 100 |
Default allowed MIME types:
- Images: jpeg, png, gif, webp
- Documents: pdf, doc, docx, txt
- Archives: zip
Modify ALLOWED_FILE_TYPES in .env to customize.
-
JWT Secret: Generate a strong secret for production:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" -
HTTPS: Use HTTPS in production (configure via reverse proxy)
-
Rate Limiting: Adjust limits based on your needs
-
File Validation: Files are validated by MIME type and size
-
Path Traversal: All file paths are sanitized
-
Password Requirements:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message",
"details": [] // Optional validation details
}
}Common error codes:
UNAUTHORIZED- Authentication requiredFORBIDDEN- Access deniedNOT_FOUND- Resource not foundVALIDATION_ERROR- Request validation failedRATE_LIMIT_EXCEEDED- Too many requestsPAYLOAD_TOO_LARGE- File too largeUNSUPPORTED_MEDIA_TYPE- File type not allowed
# Run in development mode with auto-reload
npm run dev
# Sync database (creates/updates tables)
npm run db:sync
# Seed database with sample data
npm run db:seed
# Force sync (drops all tables - DANGEROUS)
npm run db:sync -- --forceMIT License
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request