Backend API for handling chunked audio uploads from browsers and mobile devices.
- TypeScript
- Express
- In-memory storage
# 1. Install dependencies
npm install
# 2. Start the server (in a separate terminal)
npm run dev
# 3. Run the test script
./test_all.shThe test script will automatically split the sample audio file, upload it in chunks, and verify the complete flow works correctly.
npm installnpm run devnpm run build
npm startThe server will run on http://localhost:3000 by default.
Endpoint: POST /start
Request Body:
{
"fileName": "audio.mp3",
"fileType": "mp3",
"fileSize": 1048576,
"chunkCount": 10
}Response:
{
"uploadId": "550e8400-e29b-41d4-a716-446655440000"
}Endpoint: POST /part
Content-Type: multipart/form-data
Form Fields:
uploadId: string (the uploadId from /start)chunkNumber: number (0-based index)chunk: file (binary chunk data)
Response:
{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"chunkNumber": 0,
"status": "Uploaded"
}Endpoint: GET /status/:uploadId
Response:
{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "InProgress",
"chunkCount": 10,
"chunks": [
{ "chunkNumber": 0, "status": "Uploaded" },
{ "chunkNumber": 1, "status": "Uploaded" },
{ "chunkNumber": 2, "status": "Pending" },
...
]
}Status Values:
InProgress: Upload session is active, chunks are being uploadedComplete: All chunks uploaded and file has been assembled
Endpoint: POST /complete
Request Body:
{
"uploadId": "550e8400-e29b-41d4-a716-446655440000"
}Success Response:
{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"finalFileSize": 1048576,
"filePath": "/Users/murali/Code/LivaAI/uploads/sample.mp3"
}Notes:
- The assembled file is automatically saved to the
uploads/directory /completeis idempotent: calling it multiple times returns the cached result without re-stitching- After successful completion, the upload status changes from
InProgresstoComplete
Error Response (if incomplete):
{
"error": "Upload incomplete. Missing chunks: 2, 5, 7"
}✅ Out-of-order chunks: Chunks can arrive in any order
✅ Retry handling: Same chunk can be sent multiple times (overwrites previous)
✅ Validation: /complete validates all chunks are present
✅ No corruption: Chunks are reassembled in correct order
✅ Idempotent completion: Calling /complete multiple times is safe - returns cached result without re-stitching
✅ Upload status tracking: Track overall upload state (InProgress → Complete)
Run the comprehensive test script that automatically tests all API endpoints:
./test_all.shWhat it does:
- Splits
media/sample.mp3into 5 chunks - Creates an upload session via
POST /start - Uploads chunks out-of-order (2, 0, 4, 1, 3) to test ordering
- Re-uploads chunk 2 to test retry/overwrite functionality
- Checks status via
GET /status/:uploadId - Completes the upload via
POST /complete - Verifies final file size matches original
- Performs binary comparison to ensure uploaded file is byte-for-byte identical to original
- Tests idempotency by calling
/completeagain (should return cached result) - Cleans up temporary files
Sample output:
========================================
Chunked Audio Upload API Test
========================================
File Information:
- Name: sample.mp3
- Size: 307200 bytes
- Type: mp3
- Chunks: 5
✓ File split complete
✓ Upload session created: 550e8400-e29b-41d4-a716-446655440000
✓ All chunks uploaded
✓ Status retrieved
✓ Retry test complete
✓ Upload completed
Step 6: Verifying uploaded file
✓ File size verification: PASSED
Original: 307453 bytes
Final: 307453 bytes
Uploaded file location: /Users/murali/Code/LivaAI/uploads/sample.mp3
✓ Binary verification: PASSED
Files are identical (byte-for-byte match)
Test that the API correctly rejects incomplete uploads:
./test_incomplete.shWhat it does:
- Creates an upload session
- Attempts to call
/completewithout uploading any chunks - Verifies the API returns an error message
curl -X POST http://localhost:3000/start \
-H "Content-Type: application/json" \
-d '{
"fileName": "test.mp3",
"fileType": "mp3",
"fileSize": 1024,
"chunkCount": 3
}'# Chunk 0
curl -X POST http://localhost:3000/part \
-F "uploadId=YOUR_UPLOAD_ID" \
-F "chunkNumber=0" \
-F "chunk=@chunk0.bin"
# Chunk 1
curl -X POST http://localhost:3000/part \
-F "uploadId=YOUR_UPLOAD_ID" \
-F "chunkNumber=1" \
-F "chunk=@chunk1.bin"
# Chunk 2
curl -X POST http://localhost:3000/part \
-F "uploadId=YOUR_UPLOAD_ID" \
-F "chunkNumber=2" \
-F "chunk=@chunk2.bin"curl http://localhost:3000/status/YOUR_UPLOAD_IDcurl -X POST http://localhost:3000/complete \
-H "Content-Type: application/json" \
-d '{"uploadId": "YOUR_UPLOAD_ID"}'/Users/murali/Code/LivaAI/
├── src/
│ ├── index.ts # Server entry point
│ ├── app.ts # Express app configuration
│ ├── types/
│ │ └── upload.types.ts # TypeScript interfaces
│ ├── validators/
│ │ └── upload.validators.ts # Request validation functions
│ ├── services/
│ │ └── upload.service.ts # Business logic & in-memory storage
│ └── routes/
│ └── upload.routes.ts # API endpoints
├── media/
│ └── sample.mp3 # Sample audio file for testing
├── test_all.sh # Comprehensive API test script
├── test_incomplete.sh # Incomplete upload test script
├── package.json
├── tsconfig.json
└── README.md