These packages provide focused utilities for working with HTTP headers, MIME types, and error responses in Node.js applications. content-disposition and content-type parse and format specific HTTP headers. http-errors creates standard HTTP error objects with appropriate status codes and messages. mime and mime-types handle MIME type lookups and extensions, while type-is checks request content types against expected values. Together, they form a foundational toolkit for building robust HTTP servers and middleware.
When building HTTP servers or middleware in Node.js, you'll frequently encounter tasks like parsing headers, validating content types, generating proper error responses, and mapping file extensions to MIME types. The six packages in this comparison (content-disposition, content-type, http-errors, mime, mime-types, and type-is) each solve specific problems in this space with minimal, focused APIs. Let's explore how they work together and when to use each one.
The content-disposition package handles the Content-Disposition header, which tells browsers how to handle responses — particularly for file downloads.
Why not DIY? Manually constructing this header is risky because filenames can contain characters that break header parsing or enable response splitting attacks.
// Using content-disposition
const contentDisposition = require('content-disposition');
// Safe filename handling with proper encoding
const header = contentDisposition('résumé.pdf');
// Returns: 'inline; filename="resume.pdf"; filename*=UTF-8\'\'r%C3%A9sum%C3%A9.pdf'
// For attachments (downloads)
const attachmentHeader = contentDisposition('report.xlsx', { type: 'attachment' });
Without this package, you'd need to implement RFC 6266 compliance yourself, including UTF-8 encoding for international filenames and proper escaping.
The content-type package parses and formats Content-Type headers according to RFC 7231.
Why not DIY? Content-Type headers can include parameters (like charset=utf-8) and have subtle parsing rules around case sensitivity and whitespace.
// Using content-type
const contentType = require('content-type');
// Parsing a header
const parsed = contentType.parse('text/html; charset=utf-8');
// Returns: { type: 'text/html', parameters: { charset: 'utf-8' } }
// Formatting back to a string
const formatted = contentType.format(parsed);
// Returns: 'text/html; charset=utf-8'
// Works with request objects too
const reqType = contentType.parse(req);
Manual parsing would require handling edge cases like quoted parameters, multiple parameters, and ensuring the main type is normalized to lowercase.
The http-errors package creates HTTP error objects with proper status codes, messages, and properties.
Why not DIY? Creating consistent error objects that work well with frameworks like Express requires setting multiple properties correctly.
// Using http-errors
const createError = require('http-errors');
// Creating specific errors
const notFound = createError(404, 'User not found');
const badRequest = createError.BadRequest('Invalid email format');
const unauthorized = createError.Unauthorized();
// Errors have proper properties
console.log(notFound.status); // 404
console.log(notFound.expose); // true (safe to show to users)
Without this, you'd manually set statusCode, status, expose, and other properties that Express and other middleware expect.
Both mime and mime-types handle MIME type lookups, but with different scopes.
// Using mime
const mime = require('mime');
// Extension to type
const type = mime.getType('file.txt'); // 'text/plain'
// Type to extension
const ext = mime.getExtension('text/html'); // 'html'
// Custom types
mime.define({ 'application/custom': ['custom'] });
// Using mime-types
const mime = require('mime-types');
// Extension to type only
const type = mime.lookup('file.txt'); // 'text/plain'
// No reverse lookup available
// mime.extension('text/html') doesn't exist
// Get charset for a type
const charset = mime.charset('text/html'); // 'UTF-8'
Choose mime if you need bidirectional lookups or custom type definitions. Choose mime-types if you only need extension-to-type mapping and want a smaller dependency.
The type-is package checks if a request's Content-Type matches expected values.
Why not DIY? Content-Type validation needs to handle wildcards, specific types, and arrays of acceptable types while ignoring parameters.
// Using type-is
const typeis = require('type-is');
// Check against single type
if (typeis(req, 'application/json')) {
// Handle JSON
}
// Check against array of types
if (typeis(req, ['json', 'urlencoded'])) {
// Handle form data or JSON
}
// Wildcard support
if (typeis(req, 'application/*')) {
// Handle any application type
}
// Get the actual matching type
const match = typeis(req, ['json', 'html']);
// Returns 'json', 'html', or false
Manual checking would require parsing the Content-Type header first and then implementing matching logic for wildcards and aliases.
These packages are designed to complement each other in typical server scenarios:
// Example: File download endpoint
const express = require('express');
const createError = require('http-errors');
const contentDisposition = require('content-disposition');
const mime = require('mime');
app.get('/download/:filename', (req, res, next) => {
const { filename } = req.params;
// Validate file exists (simplified)
if (!fileExists(filename)) {
return next(createError.NotFound('File not found'));
}
// Set proper Content-Type
const contentType = mime.getType(filename) || 'application/octet-stream';
res.setHeader('Content-Type', contentType);
// Set safe Content-Disposition
res.setHeader('Content-Disposition', contentDisposition(filename));
// Send file
res.sendFile(filename);
});
// Example: API endpoint with validation
const typeis = require('type-is');
const createError = require('http-errors');
app.post('/api/users', (req, res, next) => {
// Validate content type
if (!typeis(req, ['json'])) {
return next(createError.UnsupportedMediaType('Only JSON accepted'));
}
// Process JSON body
// ...
});
If you're already using mime, there's no need to also install mime-types. The mime package includes everything mime-types provides plus additional functionality.
Never construct Content-Disposition headers manually:
// ❌ Dangerous - vulnerable to response splitting
res.setHeader('Content-Disposition', `attachment; filename="${userFilename}"`);
// ✅ Safe - properly escaped and encoded
res.setHeader('Content-Disposition', contentDisposition(userFilename));
Mixing manual error objects with http-errors can lead to inconsistent behavior in error-handling middleware:
// ❌ Inconsistent
if (!user) {
const err = new Error('Not found');
err.statusCode = 404;
next(err);
}
// ✅ Consistent
if (!user) {
next(createError.NotFound('User not found'));
}
| Scenario | Recommended Package(s) |
|---|---|
| Serving file downloads | content-disposition + mime |
| Parsing request Content-Type | content-type + type-is |
| Creating API error responses | http-errors |
| Static file server | mime-types (lighter) or mime (full-featured) |
| Validating request body format | type-is |
| Generating Content-Type headers | mime |
These packages follow the Unix philosophy of doing one thing well. Rather than building monolithic utilities, they provide composable, focused tools that handle the tricky details of HTTP standards correctly.
content-disposition whenever you're setting that header — the security benefits alone justify the dependency.content-type for parsing/formatting Content-Type headers — it handles RFC compliance so you don't have to.http-errors for all error creation — it ensures consistency across your application.mime and mime-types based on whether you need bidirectional lookups.type-is for content type validation — it's much more reliable than manual string matching.By using these specialized packages, you avoid common security vulnerabilities, ensure standards compliance, and write more maintainable code that clearly expresses its intent.
Choose content-disposition when you need to safely generate or parse the Content-Disposition header for file downloads. It properly handles filename encoding for special characters and prevents response splitting attacks by escaping newlines. This is essential for any application serving user-uploaded files or generating downloadable content.
Choose content-type when you need to parse or format Content-Type headers according to RFC 7231. It correctly handles media types with parameters (like charset) and provides normalized access to type and subtype. Use this instead of manual string parsing to avoid common pitfalls with parameter parsing and case sensitivity.
Choose http-errors when you need to create HTTP error objects that follow standard conventions. It provides named constructors for all HTTP status codes (like createError.NotFound()) and ensures errors have proper status codes, message properties, and expose settings. This is crucial for consistent error handling in Express.js applications and custom middleware.
Choose mime when you need a comprehensive MIME type database with both type-to-extension and extension-to-type lookups. It includes a large built-in database and supports custom type definitions. Use this over mime-types if you need extension lookups or want a single package that handles both directions of MIME type resolution.
Choose mime-types when you only need extension-to-MIME-type lookups and want a lighter dependency. It's essentially the lookup functionality extracted from the mime package but without extension-to-type support. This is ideal for static file servers that map file extensions to Content-Type headers without needing reverse lookups.
Choose type-is when you need to check if an incoming request's Content-Type matches expected values. It supports wildcards (like application/*), specific types, and arrays of acceptable types. This is commonly used in middleware to validate request bodies before processing, ensuring your handlers only receive appropriately formatted data.
Create and parse HTTP Content-Disposition header
$ npm install content-disposition
const contentDisposition = require('content-disposition')
Create an attachment Content-Disposition header value using the given file name,
if supplied. The filename is optional and if no file name is desired, but you
want to specify options, set filename to undefined.
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
note HTTP headers are of the ISO-8859-1 character set. If you are writing this
header through a means different from setHeader in Node.js, you'll want to specify
the 'binary' encoding in Node.js.
contentDisposition accepts these properties in the options object.
If the filename option is outside ISO-8859-1, then the file name is actually
stored in a supplemental field for clients that support Unicode file names and
a ISO-8859-1 version of the file name is automatically generated.
This specifies the ISO-8859-1 file name to override the automatic generation or
disables the generation all together, defaults to true.
false will disable including a ISO-8859-1 file name and only include the
Unicode version (unless the file name is already ISO-8859-1).true will enable automatic generation if the file name is outside ISO-8859-1.If the filename option is ISO-8859-1 and this option is specified and has a
different value, then the filename option is encoded in the extended field
and this set as the fallback field, even though they are both ISO-8859-1.
Specifies the disposition type, defaults to "attachment". This can also be
"inline", or any other value (all values except inline are treated like
attachment, but can convey additional information if both parties agree to
it). The type is normalized to lower-case.
const disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
Parse a Content-Disposition header string. This automatically handles extended
("Unicode") parameters by decoding them and providing them under the standard
parameter name. This will return an object with the following properties (examples
are shown for the string 'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'):
type: The disposition type (always lower case). Example: 'attachment'
parameters: An object of the parameters in the disposition (name of parameter
always lower case and extended versions replace non-extended versions). Example:
{filename: "€ rates.txt"}
const contentDisposition = require('content-disposition')
const destroy = require('destroy')
const fs = require('fs')
const http = require('http')
const onFinished = require('on-finished')
const filePath = '/path/to/public/plans.pdf'
http.createServer(function onRequest (req, res) {
// set headers
res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Disposition', contentDisposition(filePath))
// send file
const stream = fs.createReadStream(filePath)
stream.pipe(res)
onFinished(res, function () {
destroy(stream)
})
})
$ npm test