Video files are massive, but one of the most popular–and essential–parts of how people use the internet today. According to Statista, video content accounted for over 80% of global internet traffic in 2023. For developers, mastering Node.js video compression is essential to optimize bandwidth, accelerate uploads, and enhance user experiences.
In this article, I’ll guide you through compressing videos with Node.js, utilizing FFmpeg for efficient local processing, automating workflows, and scaling video management with Cloudinary’s robust transformation APIs.
In this article:
- Why Compression is Important for Video Workflows
- Setting Up a Node.js Environment for Video Processing
- Using FFmpeg with Node.js for Video Compression
- Automating Video Compression in Server-Side Workflows
- Node.js Video Compression At Scale Through Cloudinary
- Working with Different Video Formats and Codecs
Why Compression is Important for Video Workflows
Effective video compression, especially with Node.js, is vital for the performance, cost-effectiveness, and user experience of today’s web applications. Here’s why:
- Faster Load Times: Compressed videos reduce page load times, improving performance and user retention.
- Bandwidth Savings: Smaller files lower hosting costs and data usage, crucial as videos drive over 80% of internet traffic (as mentioned before).
- Smooth Playback: Proper compression ensures seamless streaming, even on mobile networks.
- Scalable Workflows: Tools like FFmpeg and Cloudinary streamline processing for high-volume video uploads.
- Device Compatibility: Compression enables format and resolution optimization for diverse devices.
By using Node.js video compression, developers can balance quality and efficiency, delivering fast, scalable, and user-friendly video experiences.
Setting Up a Node.js Environment for Video Processing
Before we dive into compressing videos using Node.js, let’s first make sure our development environment is all set up. We’ll be using Node.js to write and run our video compression scripts, so the first step is to install Node if you haven’t already.
Head over to the official Node.js website and download the latest LTS version. For this tutorial, you’ll need at least Node.js v20.15.1 and npm v10.8.3. Once installed, open your terminal or command prompt and run:
node -v npm -v
This will confirm that both Node and npm are properly installed.
Next, you need to install FFmpeg. So, head over to the official FFmpeg website and download and install FFmpeg using the instructions on the website. Once installed, open up your terminal and type the following command to check if FFmpeg was installed correctly:
ffmpeg -version
You should see an output that confirms the version of FFmpeg installed. This means everything is set up correctly so far.
Now, let’s initialize a new Node.js project where we’ll build our compression script. Create a new directory and run the following command inside it:
npm init -y
This generates a basic package.json
file that will manage our dependencies.
Finally, install the fluent-ffmpeg
library using the following command:
npm install fluent-ffmpeg
At this point, we have everything we need to start compressing videos from our Node.js app.
Using FFmpeg with Node.js for Video Compression
With everything ready, we can create a basic video compression function using FFmpeg through the fluent-ffmpeg
library.
So, start by opening up your project directory in the editor of your choice. Next, create a new JS file and start by importing the required modules:
const ffmpeg = require('fluent-ffmpeg'); const path = require('path');
Here, fluent-ffmpeg
gives us an easy interface to FFmpeg’s complex command-line tools, while path
helps us safely build file paths across different operating systems.
Next, let’s define a simple function that takes an input video path and compresses it to an output path:
function compressVideo(inputPath, outputPath) { ffmpeg(inputPath) .outputOptions([ '-vcodec libx264', // Use H.264 codec for video compression '-crf 28', // Set quality: lower CRF = higher quality '-preset fast' // Trade-off between compression speed and efficiency ]) .on('end', () => { console.log('Compression complete.'); }) .on('error', err => { console.error('Error:', err.message); }) .save(outputPath); }
In this code, we start with ffmpeg(inputPath)
, which loads the video we want to compress. The .outputOptions
method lets us define how the compression should work. For now, we use the H.264 codec for compatibility, set a CRF value of 28 to reduce file size while keeping decent quality, and apply the fast
preset to speed things up. The .on
callback runs when the compression finishes, and .on('error', ...
helps catch and log any errors. Finally, .save(outputPath)
tells FFmpeg where to write the compressed video.
Now all you need to do is call this function with paths to your original and compressed videos:
compressVideo( "path\\to\\your\\video.mp4", "path\\to\\your\\compressed\\video.mp4" );
Once the script runs, you’ll get a smaller video file in the destination path, helping you save both storage and upload time.
Automating Video Compression in Server-Side Workflows
When you’re dealing with just a handful of video files, running compression manually might be fine. But once your app starts accepting user uploads at scale, you need a reliable and automated server-side workflow to handle the compression efficiently, and without introducing bottlenecks.
A practical approach here is to hook into your backend’s upload handling logic (whether it’s built with Express, NestJS, or something else) and invoke FFmpeg compression automatically after a video is uploaded to your server. You can queue up jobs using task schedulers like BullMQ or Agenda, so that compression happens asynchronously, keeping your API responses fast and responsive.
So let’s take a look at how you can automate video compression using Express, Multer for handling uploads, and fluent-ffmpeg for compression.
Let’s begin by installing the above libraries using npm:
npm install express multer fluent-ffmpeg
Next, open up a new JS file and start by importing the required packages:
const express = require('express'); const multer = require('multer'); const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs');
Next, initialize your Express app and configure Multer to store uploaded files in a temporary directory:
const app = express(); const upload = multer({ dest: 'uploads/' });
Here, Multer saves each uploaded file into the uploads
folder and gives it a unique filename so nothing gets overwritten.
Now let’s build the core of our app. Here, we will build a POST route that will accept a file upload, compress the video using FFmpeg, and then send the compressed version back to the client:
app.post('/upload', upload.single('video'), (req, res) => { const inputPath = req.file.path; const outputPath = `compressed/${req.file.filename}.mp4`;
This route listens for uploads at /upload
. When a file is uploaded under the field name "video"
, Multer saves it to disk and makes it available through req.file
. We grab the file’s path and also define a new path for the compressed output, which will exist inside the compressed/
directory.
Next, we use fluent-ffmpeg
to run the compression:
app.post('/upload', upload.single('video'), (req, res) => { const inputPath = req.file.path; const outputPath = `compressed/${req.file.filename}.mp4`; ffmpeg(inputPath) .outputOptions(['-vcodec libx264', '-crf 28', '-preset fast']) ...
Once FFmpeg finishes compressing the video, we send it back to the client and clean up the original upload. This ensures the user gets the compressed file right away and your server doesn’t fill up with temporary files:
app.post('/upload', upload.single('video'), (req, res) => { const inputPath = req.file.path; const outputPath = `compressed/${req.file.filename}.mp4`; ffmpeg(inputPath) .outputOptions(['-vcodec libx264', '-crf 28', '-preset fast']) .on('end', () => { res.download(outputPath); fs.unlinkSync(inputPath); // Clean up }) ...
Once FFmpeg finishes, the .on('end')
callback fires. Here, we send the compressed video back to the client using res.download()
, and then we delete the original uploaded file to free up space with fs.unlinkSync(inputPath)
.
If there’s an error during processing, we catch it and return a clean error response:
app.post('/upload', upload.single('video'), (req, res) => { const inputPath = req.file.path; const outputPath = `compressed/${req.file.filename}.mp4`; ffmpeg(inputPath) .outputOptions(['-vcodec libx264', '-crf 28', '-preset fast']) .on('end', () => { res.download(outputPath); fs.unlinkSync(inputPath); // Clean up }) .on('error', err => { console.error(err); res.status(500).send('Compression error'); }) .save(outputPath); });
Finally, at the end of the file, we can start the server using app.listen()
. Here is what our code looks like:
const express = require('express'); const multer = require('multer'); const ffmpeg = require('fluent-ffmpeg'); const fs = require('fs'); const app = express(); const upload = multer({ dest: 'uploads/' }); app.post('/upload', upload.single('video'), (req, res) => { const inputPath = req.file.path; const outputPath = `compressed/${req.file.filename}.mp4`; ffmpeg(inputPath) .outputOptions(['-vcodec libx264', '-crf 28', '-preset fast']) .on('end', () => { res.download(outputPath); fs.unlinkSync(inputPath); // Clean up }) .on('error', err => { console.error(err); res.status(500).send('Compression error'); }) .save(outputPath); }); app.listen(3000, () => console.log('Server started on port 3000'));
Now, when you run this server and POST a video to /upload
, it will compress the file and return the optimized version immediately, without you having to run any extra scripts.
In real production apps, you’d likely go beyond this by queuing compression jobs (with something like BullMQ) or uploading directly to cloud storage like Amazon S3. But if you’re looking for a fully managed, scalable alternative with zero backend setup, that’s exactly where Cloudinary comes in. It handles uploads, compresses videos automatically, and delivers them globally—all from a single URL or API call. We’ll look at how that works next.
Node.js Video Compression At Scale Through Cloudinary
Manually compressing videos with FFmpeg works great when you’re just getting started. But as your app grows and users begin uploading more and more videos, managing that processing on your own servers can quickly become a headache. It adds complexity, increases processing time, and demands more infrastructure to handle the load, especially if you’re dealing with large files or high traffic.
Cloudinary is a powerful media platform that handles not just storage, but also compression, transformation, optimization, and delivery–all through a simple, developer-friendly API. Instead of writing complex scripts or spinning up video processing servers, you can offload all of that to Cloudinary’s global infrastructure and focus on building your app.
Want to get a headstart on integrating Cloudinary into your project? Check out our documentation for more information.
To get started, you’ll first need to create a new project directory and install the Cloudinary Node.js SDK using npm:
npm install cloudinary
Next, head over to Cloudinary and log in to your account. If you don’t have one already, you can sign up for a free account. Next, head over to the Programmable Media Dashboard tab and click on the Go to API Keys button. Here, you will find your API credentials. Copy these as we will need them.
Now open up a JS file in your project directory and start by configuring the Cloudinary API in your Node.js environment:
const cloudinary = require('cloudinary').v2; cloudinary.config({ cloud_name: 'your-cloud-name', api_key: 'your-api-key', api_secret: 'your-api-secret' });
Remember to replace the your-cloud-name
, your-api-key
, and your-api-secret
with your own credentials.
Finally, we need a video to compare our transformations and deliveries. Thankfully, Cloudinary comes with a whole host of assets that we can use, so for now, we will be using sea-turtle from the Cloudinary demo cloud.
Now you’re ready to upload videos and apply transformations like compression, resolution resizing, and format conversion–all in the cloud, no FFmpeg required.
Applying Video Transformations Using Cloudinary’s API
Cloudinary makes video compression feel like magic. You can apply transformations like resolution resizing, quality optimization, or format conversion during upload, all with a few lines of code:
cloudinary.uploader.upload('path/to/your/video.mp4', { resource_type: 'video', public_id: 'sample', eager: [ { width: 720, quality: 'auto', format: 'mp4' } ] }).then(result => { console.log('Uploaded and transformed:', result.eager[0].secure_url); }).catch(console.error);
Here, we call cloudinary.uploader.upload()
and pass the local path to the video we want to upload. The resource_type: 'video'
flag tells Cloudinary that this is a video file (not an image), while the public_id
is the name we want to give the uploaded video on Cloudinary. The real magic happens inside the eager
array. This is where we define our transformation settings:
- The
width: 720
resizes the video to 720 pixels wide, which is great for web playback. quality: 'auto'
tells Cloudinary to automatically optimize compression while preserving visual quality. This uses AI and perceptual metrics to make smart decisions about bitrate.format: 'mp4'
ensures the output is encoded in a widely supported, streamable format.
When the upload completes, the .then()
block logs the URL of the optimized video. The result is a ready-to-stream, optimized video URL that you can serve to users. Here is what the output looks like:
Cloudinary handles all the backend heavy lifting–transcoding, compressing, and caching your video globally.
Retrieving and Delivering Compressed Videos
Once your videos are uploaded, delivering them to end-users is just as smooth. Cloudinary gives you a dynamic URL builder that lets you transform videos in real-time:
const url = cloudinary.url('sample.mp4', { resource_type: 'video', transformation: [ { width: 480, crop: 'scale' }, { quality: 'auto' } ] }); console.log('Delivery URL:', url);
In this snippet, we’re generating a URL that returns a version of sample.mp4
scaled down to 480 pixels in width and compressed using Cloudinary’s auto
quality mode. This is perfect for delivering mobile-friendly versions of videos that load quickly without sacrificing too much visual quality. The transformation happens in real time, and Cloudinary caches the result, so future requests are served instantly.
Now let’s say you want to deliver a square-cropped version of the same video, which is very useful for social media previews or in-app thumbnails. You can do that like this:
const squareVideo = cloudinary.url('sample.mp4', { resource_type: 'video', transformation: [ { width: 480, height: 480, crop: 'fill', gravity: 'auto' }, { quality: 'auto', format: 'mp4' } ] }); console.log('Square video URL:', squareVideo);
Here we resize the video to exactly 480×480 pixels and use crop: 'fill'
to fill the frame, automatically centering the important part of the content with gravity: 'auto'
. It’s then compressed and delivered as an MP4.
You can also strip the audio track from the video entirely if you want to deliver a muted version. This can be useful for silent previews or compliance in certain environments:
const mutedVideo = cloudinary.url('sample.mp4', { resource_type: 'video', transformation: [ { effect: 'mute' }, { quality: 'auto' } ] }); console.log('Muted video URL:', mutedVideo);
Cloudinary’s transformation URLs give you full control over how videos are presented–whether you want them resized, cropped, trimmed, muted, converted to another format, or optimized in real-time. And since the CDN caches each transformed version, they load almost instantly after the first request, with no extra load on your servers.
With these tools, you can build highly dynamic and responsive video experiences with no need to pre-process or store multiple versions of your assets. Whether you’re optimizing for speed, user experience, or bandwidth, Cloudinary handles the heavy lifting–and scales effortlessly with your app.
Working with Different Video Formats and Codecs
Let’s be honest, MP4 gets all the love. But if you’re aiming for peak performance, especially across different browsers, bandwidth constraints, and use cases, understanding and leveraging different video formats and codecs becomes a must.
Each format has its trade-offs. For instance, MP4 (using H.264 compression) is widely compatible but not the most efficient in compression. WebM and AV1, on the other hand, offer superior compression ratios and great for modern browsers.
With Cloudinary, switching between formats and codecs doesn’t require re-encoding videos manually. You can simply upload once and deliver in multiple formats, depending on your audience’s needs and devices.
Converting Between Formats During Compression
Let’s start with format conversion during the upload process. Suppose you want to convert a video to WebM format and apply automatic compression while uploading. Cloudinary makes it super easy:
// Upload and convert format during upload: cloudinary.uploader.upload('path/to/your/video.mp4', { resource_type: 'video', eager: [ { format: 'webm', quality: 'auto' } // Convert to webm with auto compression ] }).then(result => { console.log('WebM URL:', result.eager[0].secure_url); });
Here, Cloudinary takes your source video and transforms it into WebM with the format
parameter. We also set the quality
parameter to auto
to ensure a great balance between file size and visual quality. Cloudinary generates and caches the transformed file immediately after upload, making it ready to serve as soon as needed.
If you’d rather convert the format on the fly, without pre-processing during upload, Cloudinary makes that easy too:
// On the fly const convertedUrl = cloudinary.url('sample.mp4', { resource_type: 'video', format: 'webm', // Convert to .webm format transformation: [ { quality: 'auto' } ] }); console.log('WebM video:', convertedUrl);
This code dynamically transforms the original MP4 to a compressed WebM file at request time. It’s incredibly useful when you need to serve different formats to different browsers (for example, MP4 for iOS and WebM for Chrome), all from the same uploaded asset.
Adjusting Resolution, Bitrate, and Frame Rate
Sometimes, format isn’t the only concern–you might need to optimize videos for mobile viewing, reduce frame rates for smoother delivery, or apply bandwidth-conscious compression. Different use cases require different optimization strategies. You might want high-resolution for desktop, low-bandwidth streams for mobile, or capped frame rates for smoother delivery. Cloudinary lets you control all of this through simple transformation parameters.
Here’s an example that combines multiple video optimizations in one go:
// Upload and convert format during upload: const transformedUrl = cloudinary.url('sample', { resource_type: 'video', transformation: [ { width: 720, crop: 'scale' }, // Resize to 720p { quality: 'auto:eco' }, // Smart compression (can be 'auto', 'auto:eco', etc.) { video_codec: 'auto' }, // Optimal codec { fps: "24" } // Set max frame rate to 24fps ], format: 'mp4' }); console.log('Optimized video URL:', transformedUrl);
In this transformation, the video is scaled down to 720 pixels in width, a common resolution for desktop and tablet screens. The auto:eco
setting for the quality parameter applies a smart compression preset optimized for good visual clarity. By setting the codec to auto
, Cloudinary selects the most efficient codec for the device and browser requesting the video. We cap the frame rate at 24fps, a common standard for cinematic playback that also reduces file size.
You can also bake all of these settings directly into the upload using eager transformations, like this:
// On the fly cloudinary.uploader.upload('path/to/your/video.mp4', { resource_type: 'video', eager: [ { width: 1080, crop: 'limit', quality: 'auto:eco', fps: '25', format: 'AVIF' } ] }).then(result => { console.log('Compressed & resized video:', result.eager[0].secure_url); });
Here we upload a video and generate an AVIF version, resized to a maximum of 1080 pixels wide using crop: 'limit'
to avoid upscaling smaller videos. The result is compressed to 25fps and served in AVIF–a next-gen format known for its superior compression ratios.
With all of these tools at your fingertips, you can adapt your videos to any device, network condition, or user experience goal, without ever manually reprocessing your files. Cloudinary handles the transformations, caching, and delivery seamlessly behind the scenes.
Deliver Efficient Videos, Every Time
Video compression is a non-negotiable aspect of modern web development, and in this article, we’ve explored practical ways to achieve it using Node.js video compression techniques. We covered setting up a Node.js environment with FFmpeg to compress videos manually or automatically, offering developers precise control over the process. For handling large-scale workflows Cloudinary’s scalable platform simplifies video management, from upload to delivery, with powerful transformation APIs.
While FFmpeg excels for local processing, Cloudinary automates compression, format selection, and delivery, ensuring videos load quickly and look great everywhere. Users demand instant playback without buffering, and these tools make that possible with minimal effort.
Ready to streamline your video uploads and enhance user experience? Start using Cloudinary today!
Learn more:
Adding Watermarks, Credits, Badges, and Text Overlays to Images