Node.js

How to serve an image using nodejs

25 September 2026 · 13 min read

How to serve an image using nodejs

In today’s visually-driven digital landscape, efficiently delivering images is paramount for any web application. Whether you’re building a personal blog, an e-commerce platform, or a robust content management system, knowing how to serve an image using Node.js effectively is a fundamental skill. Node.js, with its non-blocking I/O model and versatile ecosystem, offers several powerful ways to handle image requests, from simple static file serving to complex dynamic image manipulation. This guide will walk you through the essential techniques, best practices, and considerations for delivering images to your users with optimal performance and reliability. By understanding these methods, you can ensure your application loads quickly, provides a seamless user experience, and scales efficiently as your image assets grow.

Understanding Image Serving in Node.js

Serving images in Node.js fundamentally involves responding to an HTTP request for an image file by sending the file’s binary data back to the client. This process requires careful handling of file paths, MIME types, and HTTP headers to ensure browsers correctly interpret and display the image. While the core concept is straightforward, implementing it efficiently requires an understanding of Node.js’s asynchronous nature and its powerful built-in modules, such as fs (file system).

The choice of method to serve an image using Node.js often depends on the application’s specific needs. For static assets that don’t change frequently, a simple static file server is usually sufficient and highly performant. However, for applications requiring dynamic image generation, resizing on the fly, or serving user-uploaded content from various storage locations, a more sophisticated approach involving streaming or dedicated middleware becomes necessary. Properly configuring caching headers is also crucial to reduce server load and improve load times for returning visitors, a key aspect of modern web performance.

Why Node.js for Image Serving?

Node.js excels in I/O-bound operations, making it an excellent choice for serving files like images. Its event-driven, non-blocking architecture allows it to handle many concurrent requests without getting bogged down, which is vital when dealing with numerous image fetches. Frameworks like Express.js simplify the process significantly, providing robust tools for routing and middleware that streamline asset delivery. This efficiency translates directly into faster page loads and a better user experience, which are critical metrics for any web application’s success.

The Express.js Way: Serving Static Files

For most applications, the simplest and most common method to serve an image using Node.js is by using the static file serving capabilities of the Express.js framework. Express makes it incredibly easy to expose a directory of files (like images, CSS, or JavaScript) directly to the web browser. This approach is highly efficient because Express handles all the underlying complexities of reading files, setting correct MIME types, and managing HTTP caching headers.

To implement this, you typically designate one or more directories as “static” directories. When a request comes in for a file that matches a path within these directories, Express automatically serves it. For instance, if you have an images folder in your project root, you can configure Express to serve files from it. This method is ideal for images that are part of your application’s design or content and don’t require any server-side processing before being sent to the client. It’s a foundational technique for building performant web applications.

Serving static files is particularly efficient because web servers are optimized for this task. By offloading static asset serving to Express’s built-in capabilities, developers can focus on dynamic content and application logic, while still ensuring that images are delivered quickly and reliably. This approach is also beneficial for maintaining a clear separation of concerns within your project structure, making it easier to manage and scale your application’s assets.

To serve an image using Node.js and Express, you need to use the express.static() middleware. This middleware function takes the root directory from which to serve static assets. For example, if your images are in a folder named public/images, you would configure Express to serve the public directory. When a request for /images/my-picture.jpg comes in, Express will look for public/images/my-picture.jpg and send it if found. This is the most straightforward and recommended way for static content.

  1. Install Express.js: If you haven’t already, initialize your project and install Express: npm init -y then npm install express.
  2. Create a Static Directory: Make a folder, e.g., public, at your project’s root. Inside public, create an images subfolder and place your images there.
  3. Configure Express to Serve Static Files: In your main application file (e.g., app.js or server.js), add the following line: ``` const express = require(’express’); const app = express(); const path = require(‘path’); app.use(express.static(path.join(__dirname, ‘public’))); app.listen(3000, () => { console.log(‘Server running on port 3000’); });
  4. Access Images: Now, you can access your images directly in the browser. If you have public/images/logo.png, it will be available at http://localhost:3000/images/logo.png.

Advanced Image Serving: Dynamic and Streamed Content

While static file serving is excellent for fixed assets, many applications require more dynamic approaches to serve an image using Node.js. This includes scenarios where images are stored in a database, need to be resized on the fly, or are uploaded by users and stored in a non-public directory. In such cases, you might use Node.js’s built-in fs (file system) module in conjunction with streams to read and send image data directly.

Streaming is particularly advantageous for large images because it allows the server to send parts of the file as they are read, rather than waiting for the entire file to be loaded into memory. This reduces memory footprint and improves responsiveness, especially for users on slower connections. You’ll typically set the Content-Type HTTP header explicitly to the correct MIME type (e.g., image/jpeg, image/png) and the Content-Disposition header if you want to suggest a filename for download. This method provides greater control over how images are delivered and allows for custom logic, such as authentication checks before serving an image.

Handling Specific Image Requests

For images that aren’t static, you can create a specific route in your Express application to handle requests for them. This route would typically parse a parameter from the URL (e.g., an image ID or filename), locate the image on the server (or a storage service), and then stream it back to the client. This is common for profile pictures, generated thumbnails, or secure documents. According to MDN Web Docs, setting the correct Content-Type header is critical for the browser to correctly render the media.

const express = require('express'); const app = express(); const fs = require('fs'); const path = require('path'); app.get('/dynamic-image/:filename', (req, res) => { const filename = req.params.filename; const imagePath = path.join(__dirname, 'uploads', filename); // Assuming images are in an 'uploads' folder fs.access(imagePath, fs.constants.F_OK, (err) => { if (err) { console.error(Image not found: ${imagePath}); return res.status(404).send('Image not found'); } const ext = path.extname(filename).toLowerCase(); let contentType = 'application/octet-stream'; // Default generic type if (ext === '.jpg' || ext === '.jpeg') { contentType = 'image/jpeg'; } else if (ext === '.png') { contentType = 'image/png'; } else if (ext === '.gif') { contentType = 'image/gif'; } else if (ext === '.webp') { contentType = 'image/webp'; } res.setHeader('
<b>Question & Answer : </b><br></br><p>I have a logo that is residing at the public/images/logo.gif. Here is my nodejs code.</p> http.createServer(function(req, res){ res.writeHead(200, {'Content-Type': 'text/plain' }); res.end('Hello World \n'); }).listen(8080, '127.0.0.1');  <p>It works but when I request for localhost:8080/logo.gif then I obviously don't get the logo.</p> <p>What changes I need to do to serve an image.</p>
<br></br><h1>2016 Update</h1> <h2>Examples with Express and without Express that <em>actually work</em></h2> <p>This question is over 5 years old but <strong>every answer has some problems</strong>.</p> <h2>TL;DR</h2> <p>Scroll down for examples to serve an image with:</p> <ol> <li>express.static</li> <li>express</li> <li>connect</li> <li>http</li> <li>net</li> </ol> <p>All of the examples are also on GitHub: <a href="https://github.com/rsp/node-static-http-servers" rel="noreferrer">https://github.com/rsp/node-static-http-servers</a></p> <p>Test results are available on Travis: <a href="https://travis-ci.org/rsp/node-static-http-servers" rel="noreferrer">https://travis-ci.org/rsp/node-static-http-servers</a></p> <h2>Introduction</h2> <p>After over 5 years since this question was asked there is only <a href="https://stackoverflow.com/a/5823807/613198"><strong>one correct answer</strong></a> by <strong>generalhenry</strong> but even though that answer has no problems with the code, it seems to have some problems with <strong>reception</strong>. It was commented that it <em>"doesn't explain much other than how to rely on someone else to get the job done"</em> and the fact how many people have voted this comment up clearly shows that a lot of things need clarification.</p> <p>First of all, a good answer to "How to serve images using Node.js" is not implementing a <strong>static file server from scratch</strong> and doing it badly. A good answer is <strong>using a module</strong> like Express that <strong>does the job correctly</strong>.</p> <p>Answering comments that say that using Express <em>"doesn't explain much other than how to rely on someone else to get the job done"</em> it should be noted, that using the http module <strong>already</strong> relies on someone else to get the job done. If someone doesn't want to rely on anyone to get the job done then <strong>at least</strong> raw TCP sockets should be used instead - which I do in one of my examples below.</p> <p>A more serious problem is that all of the answers here that use the http module are <strong>broken</strong>. They introduce <strong>race conditions</strong>, <strong>insecure path resolution</strong> that will lead to <strong>path traversal vulnerability</strong>, <strong>blocking I/O</strong> that will completely <strong>fail to serve any concurrent requests</strong> at all and other subtle problems - they are completely broken as examples of what the question asks about, and yet they already use the abstraction that is provided by the http module instead of using TCP sockets so they don't even do everything from scratch as they claim.</p> <p>If the question was "How to implement static file server from scratch, as a learning exercise" then by all means answers how to do that should be posted - but even then we should expect them to at least be <strong>correct</strong>. Also, it is not unreasonable to assume that someone who wants to serve an image might want to serve more images in the future so one could argue that writing a specific custom static file server that can serve only one single file with hard-coded path is somewhat shortsighted. It seems hard to imagine that anyone who searches for an answer on how to serve an image would be content with a solution that serves just a single image instead of a general solution to serve any image.</p> <p>In short, the question is how to serve an image and an answer to that is to use an appropriate module to do that in a <strong>secure, performant and reliable way</strong> that is <strong>readable, maintainable and future-proof</strong> while using the <strong>best practice</strong> of professional Node development. But I agree that a great addition to such an answer would be showing a way to implement the same functionality manually but sadly every attempt to do that has failed so far. And that is why I wrote some new examples.</p> <p>After this short introduction, here are my five examples doing the job on 5 different levels of abstraction.</p> <h2>Minimum functionality</h2> <p>Every example serves files from the public directory and supports the minimum functionality of:</p> <ul> <li>MIME types for most common files</li> <li>serves HTML, JS, CSS, plain text and images</li> <li>serves index.html as a default directory index</li> <li>responds with error codes for missing files</li> <li>no path traversal vulnerabilities</li> <li>no race conditions while reading files</li> </ul> <p>I tested every version on Node versions 4, 5, 6 and 7.</p> <h2>express.static</h2> <p>This version uses the <a href="https://expressjs.com/en/starter/static-files.html" rel="noreferrer">express.static</a> built-in middleware of the <a href="https://expressjs.com/" rel="noreferrer">express</a> module.</p> <p>This example has the most functionality and the least amount of code.</p> var path = require('path'); var express = require('express'); var app = express(); var dir = path.join(__dirname, 'public'); app.use(express.static(dir)); app.listen(3000, function () { console.log('Listening on http://localhost:3000/'); });  <h2>express</h2> <p>This version uses the <a href="https://expressjs.com/" rel="noreferrer">express</a> module but without the express.static middleware. Serving static files is implemented as a single route handler using streams.</p> <p>This example has simple path traversal countermeasures and supports a limited set of most <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types" rel="noreferrer">common MIME types</a>.</p> var path = require('path'); var express = require('express'); var app = express(); var fs = require('fs'); var dir = path.join(__dirname, 'public'); var mime = { html: 'text/html', txt: 'text/plain', css: 'text/css', gif: 'image/gif', jpg: 'image/jpeg', png: 'image/png', svg: 'image/svg+xml', js: 'application/javascript' }; app.get('*', function (req, res) { var file = path.join(dir, req.path.replace(/\/$/, '/index.html')); if (file.indexOf(dir + path.sep) !== 0) { return res.status(403).end('Forbidden'); } var type = mime[path.extname(file).slice(1)] || 'text/plain'; var s = fs.createReadStream(file); s.on('open', function () { res.set('Content-Type', type); s.pipe(res); }); s.on('error', function () { res.set('Content-Type', 'text/plain'); res.status(404).end('Not found'); }); }); app.listen(3000, function () { console.log('Listening on http://localhost:3000/'); });  <h2>connect</h2> <p>This version uses the <a href="http://senchalabs.github.com/connect" rel="noreferrer">connect</a> module which is a one level of abstraction lower than express.</p> <p>This example has similar functionality to the express version but using slightly lower-lever APIs.</p> var path = require('path'); var connect = require('connect'); var app = connect(); var fs = require('fs'); var dir = path.join(__dirname, 'public'); var mime = { html: 'text/html', txt: 'text/plain', css: 'text/css', gif: 'image/gif', jpg: 'image/jpeg', png: 'image/png', svg: 'image/svg+xml', js: 'application/javascript' }; app.use(function (req, res) { var reqpath = req.url.toString().split('?')[0]; if (req.method !== 'GET') { res.statusCode = 501; res.setHeader('Content-Type', 'text/plain'); return res.end('Method not implemented'); } var file = path.join(dir, reqpath.replace(/\/$/, '/index.html')); if (file.indexOf(dir + path.sep) !== 0) { res.statusCode = 403; res.setHeader('Content-Type', 'text/plain'); return res.end('Forbidden'); } var type = mime[path.extname(file).slice(1)] || 'text/plain'; var s = fs.createReadStream(file); s.on('open', function () { res.setHeader('Content-Type', type); s.pipe(res); }); s.on('error', function () { res.setHeader('Content-Type', 'text/plain'); res.statusCode = 404; res.end('Not found'); }); }); app.listen(3000, function () { console.log('Listening on http://localhost:3000/'); });  <h2>http</h2> <p>This version uses the <a href="https://nodejs.org/api/http.html#http_http" rel="noreferrer">http</a> module which is the lowest-level API for HTTP in Node.</p> <p>This example has similar functionality to the connect version but using even more lower-level APIs.</p> var path = require('path'); var http = require('http'); var fs = require('fs'); var dir = path.join(__dirname, 'public'); var mime = { html: 'text/html', txt: 'text/plain', css: 'text/css', gif: 'image/gif', jpg: 'image/jpeg', png: 'image/png', svg: 'image/svg+xml', js: 'application/javascript' }; var server = http.createServer(function (req, res) { var reqpath = req.url.toString().split('?')[0]; if (req.method !== 'GET') { res.statusCode = 501; res.setHeader('Content-Type', 'text/plain'); return res.end('Method not implemented'); } var file = path.join(dir, reqpath.replace(/\/$/, '/index.html')); if (file.indexOf(dir + path.sep) !== 0) { res.statusCode = 403; res.setHeader('Content-Type', 'text/plain'); return res.end('Forbidden'); } var type = mime[path.extname(file).slice(1)] || 'text/plain'; var s = fs.createReadStream(file); s.on('open', function () { res.setHeader('Content-Type', type); s.pipe(res); }); s.on('error', function () { res.setHeader('Content-Type', 'text/plain'); res.statusCode = 404; res.end('Not found'); }); }); server.listen(3000, function () { console.log('Listening on http://localhost:3000/'); });  <h2>net</h2> <p>This version uses the <a href="https://nodejs.org/api/net.html#net_net" rel="noreferrer">net</a> module which is the lowest-level API for TCP sockets in Node.</p> <p>This example has some of the functionality of the http version but the minimal and incomplete HTTP protocol has been implemented from scratch. Since it doesn't support chunked encoding it loads the files into memory before serving them to know the size before sending a response because statting the files and then loading would introduce a race condition.</p> var path = require('path'); var net = require('net'); var fs = require('fs'); var dir = path.join(__dirname, 'public'); var mime = { html: 'text/html', txt: 'text/plain', css: 'text/css', gif: 'image/gif', jpg: 'image/jpeg', png: 'image/png', svg: 'image/svg+xml', js: 'application/javascript' }; var server = net.createServer(function (con) { var input = ''; con.on('data', function (data) { input += data; if (input.match(/\n\r?\n\r?/)) { var line = input.split(/\n/)[0].split(' '); var method = line[0], url = line[1], pro = line[2]; var reqpath = url.toString().split('?')[0]; if (method !== 'GET') { var body = 'Method not implemented'; con.write('HTTP/1.1 501 Not Implemented\n'); con.write('Content-Type: text/plain\n'); con.write('Content-Length: '+body.length+'\n\n'); con.write(body); con.destroy(); return; } var file = path.join(dir, reqpath.replace(/\/$/, '/index.html')); if (file.indexOf(dir + path.sep) !== 0) { var body = 'Forbidden'; con.write('HTTP/1.1 403 Forbidden\n'); con.write('Content-Type: text/plain\n'); con.write('Content-Length: '+body.length+'\n\n'); con.write(body); con.destroy(); return; } var type = mime[path.extname(file).slice(1)] || 'text/plain'; var s = fs.readFile(file, function (err, data) { if (err) { var body = 'Not Found'; con.write('HTTP/1.1 404 Not Found\n'); con.write('Content-Type: text/plain\n'); con.write('Content-Length: '+body.length+'\n\n'); con.write(body); con.destroy(); } else { con.write('HTTP/1.1 200 OK\n'); con.write('Content-Type: '+type+'\n'); con.write('Content-Length: '+data.byteLength+'\n\n'); con.write(data); con.destroy(); } }); } }); }); server.listen(3000, function () { console.log('Listening on http://localhost:3000/'); });  <h2>Download examples</h2> <p>I posted all of the examples on GitHub with more explanation.</p> <p>Examples with express.static, express, connect, http and net:</p> <ul> <li><a href="https://github.com/rsp/node-static-http-servers" rel="noreferrer">https://github.com/rsp/node-static-http-servers</a></li> </ul> <p>Other project using only express.static:</p> <ul> <li><a href="https://github.com/rsp/node-express-static-example" rel="noreferrer">https://github.com/rsp/node-express-static-example</a></li> </ul> <h2>Tests</h2> <p>Test results are available on Travis:</p> <ul> <li><a href="https://travis-ci.org/rsp/node-static-http-servers" rel="noreferrer">https://travis-ci.org/rsp/node-static-http-servers</a></li> </ul> <p>Everything is tested on Node versions 4, 5, 6, and 7.</p> <h2>See also</h2> <p>Other related answers:</p> <ul> <li><a href="https://stackoverflow.com/questions/38441863/failed-to-load-resource-from-same-directory-when-redirecting-javascript/38442747#38442747">Failed to load resource from same directory when redirecting Javascript</a></li> <li><a href="https://stackoverflow.com/questions/38587286/onload-js-call-not-working-with-node/38587729#38587729">onload js call not working with node</a></li> <li><a href="https://stackoverflow.com/questions/40509666/sending-whole-folder-content-to-client-with-express/40510339#40510339">Sending whole folder content to client with express</a></li> <li><a href="https://stackoverflow.com/questions/40722476/loading-partials-fails-on-the-server-js/40722594#40722594">Loading partials fails on the server JS</a></li> <li><a href="https://stackoverflow.com/questions/40837359/node-js-not-serving-the-static-image/40839534#40839534">Node JS not serving the static image</a></li> </ul>