Serving Correct Mimes with Node and Express

This weekend I was looking for a dynamic way to serve mime types with nodeJS using the Express framework.First we’ll set up our server to include a couple libraries and an external javascript file that we’ll write ourselves:

require(__dirname  + '/lib/helper.js');

var express = require('express'),
    url = require('url');
    
var app = express.createServer();

The process should be fairly straight forward, grab any dynamic request:

 

app.get('/*.*', function(req, res) {

    var options = url.parse(req.url, true);

    var mime = Helper.getMime(options);
    
    serveFile(res, options.pathname, mime);

});

Express will pull in any get request that queries for something deeper than “/” in the url. It then we’ll use our url library to parse the request and get the last part of the file with no “?id=…&whatever=example…” and no base path like “http://www.example.com”. All we want is “/css/style.css” or whatever. Next we send the option string to our helper to figure out our mime type. I’ve done this by making Helper a global variable. You’ll see in the next code example. We’ll create a folder called “lib,” then we’ll place a file called “helper.js” in it and paste the following inside of it:

/* 
 * @author - Based of a file from Gist here: https://gist.github.com/1757658
 * 
 * @modified - Mike Newell - it was on Gist so I figure I can use it
 * 
 * @Description -   Added support for a few more mime types including the new
 *                  .ogv, .webm, and .mp4 file types for HTML5 video.
 * 
 */


Helper = {
    
    types: {
        "3gp" : "video/3gpp"
        , "a" : "application/octet-stream"
        , "ai" : "application/postscript"
        , "aif" : "audio/x-aiff"
        , "aiff" : "audio/x-aiff"
        , "asc" : "application/pgp-signature"
        , "asf" : "video/x-ms-asf"
        , "asm" : "text/x-asm"
        , "asx" : "video/x-ms-asf"
        , "atom" : "application/atom+xml"
        , "au" : "audio/basic"
        , "avi" : "video/x-msvideo"
        , "bat" : "application/x-msdownload"
        , "bin" : "application/octet-stream"
        , "bmp" : "image/bmp"
        , "bz2" : "application/x-bzip2"
        , "c" : "text/x-c"
        , "cab" : "application/vnd.ms-cab-compressed"
        , "cc" : "text/x-c"
        , "chm" : "application/vnd.ms-htmlhelp"
        , "class" : "application/octet-stream"
        , "com" : "application/x-msdownload"
        , "conf" : "text/plain"
        , "cpp" : "text/x-c"
        , "crt" : "application/x-x509-ca-cert"
        , "css" : "text/css"
        , "csv" : "text/csv"
        , "cxx" : "text/x-c"
        , "deb" : "application/x-debian-package"
        , "der" : "application/x-x509-ca-cert"
        , "diff" : "text/x-diff"
        , "djv" : "image/vnd.djvu"
        , "djvu" : "image/vnd.djvu"
        , "dll" : "application/x-msdownload"
        , "dmg" : "application/octet-stream"
        , "doc" : "application/msword"
        , "dot" : "application/msword"
        , "dtd" : "application/xml-dtd"
        , "dvi" : "application/x-dvi"
        , "ear" : "application/java-archive"
        , "eml" : "message/rfc822"
        , "eps" : "application/postscript"
        , "exe" : "application/x-msdownload"
        , "f" : "text/x-fortran"
        , "f77" : "text/x-fortran"
        , "f90" : "text/x-fortran"
        , "flv" : "video/x-flv"
        , "for" : "text/x-fortran"
        , "gem" : "application/octet-stream"
        , "gemspec" : "text/x-script.ruby"
        , "gif" : "image/gif"
        , "gz" : "application/x-gzip"
        , "h" : "text/x-c"
        , "hh" : "text/x-c"
        , "htm" : "text/html"
        , "html" : "text/html"
        , "ico" : "image/vnd.microsoft.icon"
        , "ics" : "text/calendar"
        , "ifb" : "text/calendar"
        , "iso" : "application/octet-stream"
        , "jar" : "application/java-archive"
        , "java" : "text/x-java-source"
        , "jnlp" : "application/x-java-jnlp-file"
        , "jpeg" : "image/jpeg"
        , "jpg" : "image/jpeg"
        , "js" : "application/javascript"
        , "json" : "application/json"
        , "log" : "text/plain"
        , "m3u" : "audio/x-mpegurl"
        , "m4v" : "video/mp4"
        , "man" : "text/troff"
        , "mathml" : "application/mathml+xml"
        , "mbox" : "application/mbox"
        , "mdoc" : "text/troff"
        , "me" : "text/troff"
        , "mid" : "audio/midi"
        , "midi" : "audio/midi"
        , "mime" : "message/rfc822"
        , "mml" : "application/mathml+xml"
        , "mng" : "video/x-mng"
        , "mov" : "video/quicktime"
        , "mp3" : "audio/mpeg"
        , "mp4" : "video/mp4"
        , "mp4v" : "video/mp4"
        , "mpeg" : "video/mpeg"
        , "mpg" : "video/mpeg"
        , "ms" : "text/troff"
        , "msi" : "application/x-msdownload"
        , "odp" : "application/vnd.oasis.opendocument.presentation"
        , "ods" : "application/vnd.oasis.opendocument.spreadsheet"
        , "odt" : "application/vnd.oasis.opendocument.text"
        , "ogg" : "application/ogg"
        , "ogv" : "video/ogg"
        , "p" : "text/x-pascal"
        , "pas" : "text/x-pascal"
        , "pbm" : "image/x-portable-bitmap"
        , "pdf" : "application/pdf"
        , "pem" : "application/x-x509-ca-cert"
        , "pgm" : "image/x-portable-graymap"
        , "pgp" : "application/pgp-encrypted"
        , "pkg" : "application/octet-stream"
        , "pl" : "text/x-script.perl"
        , "pm" : "text/x-script.perl-module"
        , "png" : "image/png"
        , "pnm" : "image/x-portable-anymap"
        , "ppm" : "image/x-portable-pixmap"
        , "pps" : "application/vnd.ms-powerpoint"
        , "ppt" : "application/vnd.ms-powerpoint"
        , "ps" : "application/postscript"
        , "psd" : "image/vnd.adobe.photoshop"
        , "py" : "text/x-script.python"
        , "qt" : "video/quicktime"
        , "ra" : "audio/x-pn-realaudio"
        , "rake" : "text/x-script.ruby"
        , "ram" : "audio/x-pn-realaudio"
        , "rar" : "application/x-rar-compressed"
        , "rb" : "text/x-script.ruby"
        , "rdf" : "application/rdf+xml"
        , "roff" : "text/troff"
        , "rpm" : "application/x-redhat-package-manager"
        , "rss" : "application/rss+xml"
        , "rtf" : "application/rtf"
        , "ru" : "text/x-script.ruby"
        , "s" : "text/x-asm"
        , "sgm" : "text/sgml"
        , "sgml" : "text/sgml"
        , "sh" : "application/x-sh"
        , "sig" : "application/pgp-signature"
        , "snd" : "audio/basic"
        , "so" : "application/octet-stream"
        , "svg" : "image/svg+xml"
        , "svgz" : "image/svg+xml"
        , "swf" : "application/x-shockwave-flash"
        , "t" : "text/troff"
        , "tar" : "application/x-tar"
        , "tbz" : "application/x-bzip-compressed-tar"
        , "tcl" : "application/x-tcl"
        , "tex" : "application/x-tex"
        , "texi" : "application/x-texinfo"
        , "texinfo" : "application/x-texinfo"
        , "text" : "text/plain"
        , "tif" : "image/tiff"
        , "tiff" : "image/tiff"
        , "torrent" : "application/x-bittorrent"
        , "tr" : "text/troff"
        , "txt" : "text/plain"
        , "vcf" : "text/x-vcard"
        , "vcs" : "text/x-vcalendar"
        , "vrml" : "model/vrml"
        , "war" : "application/java-archive"
        , "wav" : "audio/x-wav"
        , "webm" : "video/webm"
        , "wma" : "audio/x-ms-wma"
        , "wmv" : "video/x-ms-wmv"
        , "wmx" : "video/x-ms-wmx"
        , "wrl" : "model/vrml"
        , "wsdl" : "application/wsdl+xml"
        , "xbm" : "image/x-xbitmap"
        , "xhtml" : "application/xhtml+xml"
        , "xls" : "application/vnd.ms-excel"
        , "xml" : "application/xml"
        , "xpm" : "image/x-xpixmap"
        , "xsl" : "application/xml"
        , "xslt" : "application/xslt+xml"
        , "yaml" : "text/yaml"
        , "yml" : "text/yaml"
        , "zip" : "application/zip"
    },
    
    getMime: function(u) {
        
        var ext = this.getExt(u.pathname).replace('.', '');
        
        return this.types[ext.toLowerCase()] || 'application/octet-stream';
        
    },
    
    getExt: function(path) {
        var i = path.lastIndexOf('.');
        
        return (i < 0) ? '' : path.substr(i);
    }
    
};

Basically, this file sets up a really large array of objects. For each of the items in the array we store a file extension and corresponding mime type. We make a function at the bottom called “getExt()” which parses the url string we pass to it and returns the file extension. This function is called by our main function called “getMime()” which will be called from our original script. It strips the period off the file extension, then returns the mime type associated with that extension in the array.

Lastly, we create a function that will server the file dynamically and report any errors if we can’t read the file. To use the file system reader we’ll also need to require it. At the top of your server.js file add this line:

fs = require('fs')

So your file should look something like:

 require(__dirname  + '/lib/helper.js');

var express = require('express'),
    fs = require('fs'),
    url = require('url');
    
var app = express.createServer();

Now we should have a server.js file that looks like this:

require(__dirname  + '/lib/helper.js');

var express = require('express'),
    fs = require('fs'),
    url = require('url');
    
var app = express.createServer();

app.get('/*.*', function(req, res) {

    var options = url.parse(req.url, true);

    var mime = Helper.getMime(options);
    
    serveFile(res, options.pathname, mime);

});

function serveFile(res, pathName, mime) {
    
    mime = mime || 'text/html';
    
    fs.readFile(__dirname + '/' + pathName, function (err, data) {
        if (err) {
            res.writeHead(500, {"Content-Type": "text/plain"});
            return res.end('Error loading ' + pathName + " with Error: " + err);
        }
        res.writeHead(200, {"Content-Type": mime});
        res.end(data);
    });
}

And a helper.js file in the /lib folder that looks like this:

/* 
 * @author - Based of a file from Gist here: https://gist.github.com/1757658
 * 
 * @modified - Mike Newell - it was on Gist so I figure I can use it
 * 
 * @Description -   Added support for a few more mime types including the new
 *                  .ogv, .webm, and .mp4 file types for HTML5 video.
 * 
 */


Helper = {
    
    types: {
        "3gp" : "video/3gpp"
        , "a" : "application/octet-stream"
        , "ai" : "application/postscript"
        , "aif" : "audio/x-aiff"
        , "aiff" : "audio/x-aiff"
        , "asc" : "application/pgp-signature"
        , "asf" : "video/x-ms-asf"
        , "asm" : "text/x-asm"
        , "asx" : "video/x-ms-asf"
        , "atom" : "application/atom+xml"
        , "au" : "audio/basic"
        , "avi" : "video/x-msvideo"
        , "bat" : "application/x-msdownload"
        , "bin" : "application/octet-stream"
        , "bmp" : "image/bmp"
        , "bz2" : "application/x-bzip2"
        , "c" : "text/x-c"
        , "cab" : "application/vnd.ms-cab-compressed"
        , "cc" : "text/x-c"
        , "chm" : "application/vnd.ms-htmlhelp"
        , "class" : "application/octet-stream"
        , "com" : "application/x-msdownload"
        , "conf" : "text/plain"
        , "cpp" : "text/x-c"
        , "crt" : "application/x-x509-ca-cert"
        , "css" : "text/css"
        , "csv" : "text/csv"
        , "cxx" : "text/x-c"
        , "deb" : "application/x-debian-package"
        , "der" : "application/x-x509-ca-cert"
        , "diff" : "text/x-diff"
        , "djv" : "image/vnd.djvu"
        , "djvu" : "image/vnd.djvu"
        , "dll" : "application/x-msdownload"
        , "dmg" : "application/octet-stream"
        , "doc" : "application/msword"
        , "dot" : "application/msword"
        , "dtd" : "application/xml-dtd"
        , "dvi" : "application/x-dvi"
        , "ear" : "application/java-archive"
        , "eml" : "message/rfc822"
        , "eps" : "application/postscript"
        , "exe" : "application/x-msdownload"
        , "f" : "text/x-fortran"
        , "f77" : "text/x-fortran"
        , "f90" : "text/x-fortran"
        , "flv" : "video/x-flv"
        , "for" : "text/x-fortran"
        , "gem" : "application/octet-stream"
        , "gemspec" : "text/x-script.ruby"
        , "gif" : "image/gif"
        , "gz" : "application/x-gzip"
        , "h" : "text/x-c"
        , "hh" : "text/x-c"
        , "htm" : "text/html"
        , "html" : "text/html"
        , "ico" : "image/vnd.microsoft.icon"
        , "ics" : "text/calendar"
        , "ifb" : "text/calendar"
        , "iso" : "application/octet-stream"
        , "jar" : "application/java-archive"
        , "java" : "text/x-java-source"
        , "jnlp" : "application/x-java-jnlp-file"
        , "jpeg" : "image/jpeg"
        , "jpg" : "image/jpeg"
        , "js" : "application/javascript"
        , "json" : "application/json"
        , "log" : "text/plain"
        , "m3u" : "audio/x-mpegurl"
        , "m4v" : "video/mp4"
        , "man" : "text/troff"
        , "mathml" : "application/mathml+xml"
        , "mbox" : "application/mbox"
        , "mdoc" : "text/troff"
        , "me" : "text/troff"
        , "mid" : "audio/midi"
        , "midi" : "audio/midi"
        , "mime" : "message/rfc822"
        , "mml" : "application/mathml+xml"
        , "mng" : "video/x-mng"
        , "mov" : "video/quicktime"
        , "mp3" : "audio/mpeg"
        , "mp4" : "video/mp4"
        , "mp4v" : "video/mp4"
        , "mpeg" : "video/mpeg"
        , "mpg" : "video/mpeg"
        , "ms" : "text/troff"
        , "msi" : "application/x-msdownload"
        , "odp" : "application/vnd.oasis.opendocument.presentation"
        , "ods" : "application/vnd.oasis.opendocument.spreadsheet"
        , "odt" : "application/vnd.oasis.opendocument.text"
        , "ogg" : "application/ogg"
        , "ogv" : "video/ogg"
        , "p" : "text/x-pascal"
        , "pas" : "text/x-pascal"
        , "pbm" : "image/x-portable-bitmap"
        , "pdf" : "application/pdf"
        , "pem" : "application/x-x509-ca-cert"
        , "pgm" : "image/x-portable-graymap"
        , "pgp" : "application/pgp-encrypted"
        , "pkg" : "application/octet-stream"
        , "pl" : "text/x-script.perl"
        , "pm" : "text/x-script.perl-module"
        , "png" : "image/png"
        , "pnm" : "image/x-portable-anymap"
        , "ppm" : "image/x-portable-pixmap"
        , "pps" : "application/vnd.ms-powerpoint"
        , "ppt" : "application/vnd.ms-powerpoint"
        , "ps" : "application/postscript"
        , "psd" : "image/vnd.adobe.photoshop"
        , "py" : "text/x-script.python"
        , "qt" : "video/quicktime"
        , "ra" : "audio/x-pn-realaudio"
        , "rake" : "text/x-script.ruby"
        , "ram" : "audio/x-pn-realaudio"
        , "rar" : "application/x-rar-compressed"
        , "rb" : "text/x-script.ruby"
        , "rdf" : "application/rdf+xml"
        , "roff" : "text/troff"
        , "rpm" : "application/x-redhat-package-manager"
        , "rss" : "application/rss+xml"
        , "rtf" : "application/rtf"
        , "ru" : "text/x-script.ruby"
        , "s" : "text/x-asm"
        , "sgm" : "text/sgml"
        , "sgml" : "text/sgml"
        , "sh" : "application/x-sh"
        , "sig" : "application/pgp-signature"
        , "snd" : "audio/basic"
        , "so" : "application/octet-stream"
        , "svg" : "image/svg+xml"
        , "svgz" : "image/svg+xml"
        , "swf" : "application/x-shockwave-flash"
        , "t" : "text/troff"
        , "tar" : "application/x-tar"
        , "tbz" : "application/x-bzip-compressed-tar"
        , "tcl" : "application/x-tcl"
        , "tex" : "application/x-tex"
        , "texi" : "application/x-texinfo"
        , "texinfo" : "application/x-texinfo"
        , "text" : "text/plain"
        , "tif" : "image/tiff"
        , "tiff" : "image/tiff"
        , "torrent" : "application/x-bittorrent"
        , "tr" : "text/troff"
        , "txt" : "text/plain"
        , "vcf" : "text/x-vcard"
        , "vcs" : "text/x-vcalendar"
        , "vrml" : "model/vrml"
        , "war" : "application/java-archive"
        , "wav" : "audio/x-wav"
        , "webm" : "video/webm"
        , "wma" : "audio/x-ms-wma"
        , "wmv" : "video/x-ms-wmv"
        , "wmx" : "video/x-ms-wmx"
        , "wrl" : "model/vrml"
        , "wsdl" : "application/wsdl+xml"
        , "xbm" : "image/x-xbitmap"
        , "xhtml" : "application/xhtml+xml"
        , "xls" : "application/vnd.ms-excel"
        , "xml" : "application/xml"
        , "xpm" : "image/x-xpixmap"
        , "xsl" : "application/xml"
        , "xslt" : "application/xslt+xml"
        , "yaml" : "text/yaml"
        , "yml" : "text/yaml"
        , "zip" : "application/zip"
    },
    
    getMime: function(u) {
        
        var ext = this.getExt(u.pathname).replace('.', '');
        
        return this.types[ext.toLowerCase()] || 'application/octet-stream';
        
    },
    
    getExt: function(path) {
        var i = path.lastIndexOf('.');
        
        return (i < 0) ? '' : path.substr(i);
    }
    
};

So in a couple hundred lines of code we have something that serves dynamic files, anything from a correctly mimed javascript file to a .webm video.

Anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *