Build an Image Processing API with Node.js, Express, Multer, Potrace, and Sharp
In this tutorial, we’ll create a RESTful API using Node.js that handles file uploads, resizes images into multiple sizes, and converts uploaded images to SVGs using the Potrace library.
This project is perfect for learning how to work with modern libraries like Express, Multer, and Sharp while implementing best practices for secure and robust code.
Project Goals
- Accept image uploads via a POST API endpoint.
- Automatically resize images into multiple sizes (e.g., thumbnails, large, etc.).
- Convert uploaded images into SVG format for vector-based usage.
- Handle errors and secure the application.
Required Dependencies
Here are the main libraries used in this project:
- Express: A minimalist framework for creating web servers.
- Multer: Middleware for handling file uploads.
- Potrace: A library for converting images to SVG.
- Sharp: A high-performance library for resizing and processing images.
- Winston: An advanced logger for tracking errors and events.
Install these dependencies using the following command:
npm install express multer potrace sharp winston
Project Structure
Here’s an overview of the project structure:
.
├── uploads/ # Directory for storing uploaded files
├── server.js # Main application file
├── error.log # Log file for errors
└── package.json # Project dependencies and scripts
 Complete Code
Here’s the complete code for the API:
const express = require("express");
const potrace = require("potrace");
const sharp = require("sharp");
const multer = require("multer");
const winston = require("winston");
const fs = require("fs");
const path = require("path");
const app = express();
const port = process.env.PORT || 5005;
app.use(express.json()); // Middleware pour parser le JSON dans le corps de la requête
// Logger setup
const logger = winston.createLogger({
level: "info",
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: "error.log", level: "error" }),
],
});
// Configuration de multer pour stocker le fichier en local
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = "uploads/";
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, Date.now() + "-full" + path.extname(file.originalname)); // Nom unique pour chaque fichier
},
});
// Multer avec limite de taille et validation des fichiers
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // Limite à 5 Mo
fileFilter: (req, file, cb) => {
if (!file.mimetype.startsWith("image/")) {
logger.warn(`Rejected file: ${file.originalname}, MIME type: ${file.mimetype}`);
return cb(new Error("Only image files are allowed"), false);
}
cb(null, true);
},
});
const traceAndResizeImage = async (file) => {
const params = { threshold: 120 };
return new Promise((resolve, reject) => {
potrace.trace(file, params, async (err, svg) => {
if (err) return reject(err);
if (svg) {
let newFilePNG = file
try {
const newFileBase = file.replace("-full.", "-");
await sharp(file)
.resize({ height: 1200, width: 1200, fit: "fill" })
.toFile(`${newFileBase}current.png`);
await sharp(file)
.resize({ height: 500, width: 500, fit: "fill" })
.toFile(`${newFileBase}large.png`);
await sharp(file)
.resize({ height: 320, width: 280, fit: "fill" })
.toFile(`${newFileBase}medium.png`);
await sharp(file)
.resize(150, 150)
.png({ quality: 90 })
.toFile(`${newFileBase}thumb.png`);
resolve({ success: true, svg });
} catch (resizeError) {
reject(resizeError);
}
} else {
reject(new Error("SVG generation failed."));
}
});
});
};
app.post("/potrace", upload.single("file"), async (req, res) => {
if (!req.file) {
return res.status(400).json({ success: false, message: "File is required" });
}
const relativeImagePath = req.file.path;
const absoluteImagePath = path.resolve(relativeImagePath);
try {
await traceAndResizeImage(absoluteImagePath);
res.json({ success: true });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.use((req, res) => {
res.status(404).json({ success: false, message: "Route not found" });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Key Features
Secure Upload Handling:
- Strict MIME type validation for images.
- File size limitation set to 5 MB.
Image Processing:
- Resizes uploaded images into multiple sizes.
- Generates an SVG file using Potrace.
Logging:
- Winston is used for detailed logging of errors and events.
Conclusion
This project is an excellent starting point for learning how to handle file uploads, manipulate images, and implement best practices in your APIs. You can enhance this project by adding features like support for additional image formats, integration with cloud storage (S3, Cloudinary), or a queue system for handling heavy processing tasks.
Top comments (0)