In this post, you'll learn how to build a clean, reusable backend API in Node.js + TypeScript using Express and Multer to handle file uploads. We'll keep it simple — no file type restrictions or size limits — just basic upload functionality that saves files using their original name.
🛠 Tech Stack
- Express: Web framework for Node.js
-
Multer: Middleware for handling
multipart/form-data
(file uploads) - TypeScript: Optional static typing
- MVC Structure: For clean separation of logic
🗂 Folder Structure
file-upload-api/
├── controllers/
│ └── upload.controller.ts
├── middleware/
│ └── upload.middleware.ts
├── routes/
│ └── upload.route.ts
├── services/
│ └── upload.service.ts
├── uploads/ (created automatically)
├── index.ts
├── tsconfig.json
└── package.json
📦 Step 1: Install Dependencies
npm install express multer
npm install -D typescript ts-node-dev @types/node @types/express @types/multer
Add a simple tsconfig.json
if you don’t have one yet:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
},
"include": ["./**/*.ts"]
}
✍️ Step 2: Multer Middleware
middleware/upload.middleware.ts
import multer from 'multer';
import path from 'path';
import fs from 'fs';
// Ensure uploads directory exists
const uploadDir = path.join(process.cwd(), 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (_req, _file, cb) => cb(null, uploadDir),
filename: (_req, file, cb) => {
const timestamp = Date.now();
cb(null, `${timestamp}-${file.originalname}`);
},
});
export const upload = multer({ storage });
💼 Step 3: Upload Service
services/upload.service.ts
export const handleUploadService = (file: Express.Multer.File) => {
return {
message: 'File uploaded successfully!',
originalName: file.originalname,
filename: file.filename,
path: file.path,
size: file.size,
};
};
📤 Step 4: Upload Controller
controllers/upload.controller.ts
import { Request, Response } from 'express';
import { handleUploadService } from '../services/upload.service';
export const uploadFile = (req: Request, res: Response) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const data = handleUploadService(req.file);
res.status(200).json(data);
};
🛣 Step 5: Define the Route
routes/upload.route.ts
import { Router } from 'express';
import { upload } from '../middleware/upload.middleware';
import { uploadFile } from '../controllers/upload.controller';
const router = Router();
router.post('/file', upload.single('file'), uploadFile);
export default router;
🚀 Step 6: Initialize Express Server
index.ts
import express from 'express';
import uploadRoute from './routes/upload.route';
const app = express();
const PORT = 3000;
app.use(express.json());
app.use('/api/upload', uploadRoute);
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
🧪 Test It with Postman
-
Method:
POST
-
URL:
http://localhost:3000/api/upload/file
-
Body:
form-data
- Key:
file
(type:File
) - Select a file from your system
- Key:
You should get a JSON response like:
{
"message": "File uploaded successfully!",
"originalName": "video.mp4",
"filename": "1713967431914-video.mp4",
"path": "/uploads/1713967431914-video.mp4",
"size": 123456
}
✅ Conclusion
You now have a robust, scalable backend setup for handling video uploads using Express, TypeScript and Multer. It saves with original file names (timestamp-prefixed), and follows clean code separation via MVC.