expressjs/express
Static files
Active contributors: dougwilson, wesleytodd, ulisesgascon
Express has two paths for serving files:
express.static(root, [opts])— middleware that serves a directory.res.sendFile(path, [opts], [cb])— explicit per-request file streaming.
Both are powered by the send package; express.static is a direct re-export of serve-static, which itself wraps send.
express.static
// lib/express.js (excerpt)
exports.static = require('serve-static');Usage:
app.use(express.static('public'));
app.use('/assets', express.static(path.join(__dirname, 'public')));serve-static walks a request URL path under the given root, calls send(req, pathname, opts) to stream the file, and forwards anything else to next(). Notable options:
dotfiles—'allow'/'deny'/'ignore'(default).etag— boolean (defaulttrue).extensions— list of fallback extensions to try (e.g.,['html']).index— directory index file (default'index.html', orfalseto disable).lastModified— sendsLast-Modified(defaulttrue).maxAge—Cache-Control: max-agein milliseconds.redirect— redirect bare directories todir/(defaulttrue).setHeaders(res, path, stat)— hook to inject custom headers.
Because Express simply re-exports serve-static, every option in the serve-static README applies.
res.sendFile
res.sendFile is implemented in lib/response.js and delegates to the send package directly. The Express layer:
- Validates that
pathis a non-empty string and either absolute or accompanied byopts.root. ThrowsTypeErrorotherwise. - URL-encodes the pathname.
- Sets
opts.etagfromapp.enabled('etag')so the streamed file uses the same ETag policy asres.send. - Calls
send(req, encodedPath, opts)and pipes the resulting stream to the response. - Wires events:
directory→ callback withEISDIR;error→ callback;aborted→ECONNABORTED;finished→ success.
Flow
sequenceDiagram
participant Caller
participant Res as res.sendFile
participant Send as send package
participant FS as filesystem
participant Sock as response
Caller->>Res: res.sendFile(path, opts, cb)
Res->>Res: validate
Res->>Send: send(req, encodeURI(path), opts)
Send->>FS: stat + open
FS-->>Send: stream
Send->>Sock: pipe(res)
Sock-->>Res: 'finished' / 'aborted' / 'directory' / 'error'
Res-->>Caller: cb(err?)res.download
res.download(path, [name], [opts], [cb]) is a thin wrapper on top of res.sendFile. It:
- Sets
Content-Disposition: attachment; filename=...using thecontent-dispositionpackage. - Merges user-provided headers (without overriding the disposition).
- Resolves the path to absolute when no
opts.rootwas given. - Calls
res.sendFile(fullPath, opts, cb).
app.get('/report.pdf', (req, res) => {
res.download('/var/reports/report-2026-04.pdf', 'monthly-report.pdf');
});Caching and ETag
For both express.static and res.sendFile, the send package generates an ETag (when enabled) and a Last-Modified header from the file's mtime. On a subsequent request with If-None-Match or If-Modified-Since, the fresh package decides freshness and send returns 304 with no body.
The Express-level 'etag' setting (which controls in-memory ETag generation in res.send) is forwarded to send for res.sendFile (added explicitly in 5.1).
Common pitfalls
- Relative path without
rootinres.sendFilethrows. Always use absolute paths or passopts.root. - Dotfiles: by default,
serve-staticignores them. Passdotfiles: 'allow'if you really want to serve them. - Directory traversal:
sendblocks..outside the configured root. Don't try to roll your own escape hatch. - Large files: streams are piped, so memory is bounded — but
Content-Lengthmust be set bysend. If you want to add custom headers, usesetHeaders(forserve-static) oropts.headers(forres.sendFile).
Related pages
- Response for the implementation of
res.sendFileandres.download. - Reference / Dependencies for
serve-staticandsendversions.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.