https://github.com/fastify/fastify-static
Plugin for serving static files as fast as possible
https://github.com/fastify/fastify-static
fastify fastify-plugin file-server static
Last synced: 24 days ago
JSON representation
Plugin for serving static files as fast as possible
- Host: GitHub
- URL: https://github.com/fastify/fastify-static
- Owner: fastify
- License: mit
- Created: 2017-08-17T08:43:43.000Z (almost 8 years ago)
- Default Branch: main
- Last Pushed: 2025-03-30T21:32:41.000Z (2 months ago)
- Last Synced: 2025-04-25T09:07:41.561Z (about 1 month ago)
- Topics: fastify, fastify-plugin, file-server, static
- Language: JavaScript
- Homepage: https://npmjs.com/package/@fastify/static
- Size: 611 KB
- Stars: 463
- Watchers: 18
- Forks: 106
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-fastify - `fastify-static`
README
# @fastify/static
[](https://github.com/fastify/fastify-static/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@fastify/static)
[](https://github.com/neostandard/neostandard)Plugin for serving static files as fast as possible.
## Install
```
npm i @fastify/static
```### Compatibility
| Plugin version | Fastify version |
| ---------------|-----------------|
| `>=8.x` | `^5.x` |
| `^7.x` | `^4.x` |
| `>=5.x <7.x` | `^3.x` |
| `>=2.x <5.x` | `^2.x` |
| `^1.x` | `^1.x` |Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
in the table above.
See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details.## Usage
```js
const fastify = require('fastify')({logger: true})
const path = require('node:path')fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public'),
prefix: '/public/', // optional: default '/'
constraints: { host: 'example.com' } // optional: default {}
})fastify.get('/another/path', function (req, reply) {
reply.sendFile('myHtml.html') // serving path.join(__dirname, 'public', 'myHtml.html') directly
})fastify.get('/another/patch-async', async function (req, reply) {
return reply.sendFile('myHtml.html')
})fastify.get('/path/with/different/root', function (req, reply) {
reply.sendFile('myHtml.html', path.join(__dirname, 'build')) // serving a file from a different root location
})fastify.get('/another/path', function (req, reply) {
reply.sendFile('myHtml.html', { cacheControl: false }) // overriding the options disabling cache-control headers
})// Run the server!
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
```### Multiple prefixed roots
```js
const fastify = require('fastify')()
const fastifyStatic = require('@fastify/static')
const path = require('node:path')
// first plugin
fastify.register(fastifyStatic, {
root: path.join(__dirname, 'public')
})// second plugin
fastify.register(fastifyStatic, {
root: path.join(__dirname, 'node_modules'),
prefix: '/node_modules/',
decorateReply: false // the reply decorator has been added by the first plugin registration
})```
### Sending a file with `content-disposition` header
```js
const fastify = require('fastify')()
const path = require('node:path')fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public'),
prefix: '/public/', // optional: default '/'
})fastify.get('/another/path', function (req, reply) {
reply.download('myHtml.html', 'custom-filename.html') // sending path.join(__dirname, 'public', 'myHtml.html') directly with custom filename
})fastify.get('another/patch-async', async function (req, reply) {
// an async handler must always return the reply object
return reply.download('myHtml.html', 'custom-filename.html')
})fastify.get('/path/without/cache/control', function (req, reply) {
reply.download('myHtml.html', { cacheControl: false }) // serving a file disabling cache-control headers
})fastify.get('/path/without/cache/control', function (req, reply) {
reply.download('myHtml.html', 'custom-filename.html', { cacheControl: false })
})```
### Managing cache-control headers
Production sites should use a reverse-proxy to manage caching headers.
However, here is an example of using fastify-static to host a Single Page Application (for example a [vite.js](https://vite.dev/) build) with sane caching.```js
fastify.register(require('@fastify/static'), {
root: path.join(import.meta.dirname, 'dist'), // import.meta.dirname node.js >= v20.11.0
// By default all assets are immutable and can be cached for a long period due to cache bursting techniques
maxAge: '30d',
immutable: true,
})// Explicitly reduce caching of assets that don't use cache bursting techniques
fastify.get('/', function (req, reply) {
// index.html should never be cached
reply.sendFile('index.html', {maxAge: 0, immutable: false})
})fastify.get('/favicon.ico', function (req, reply) {
// favicon can be cached for a short period
reply.sendFile('favicon.ico', {maxAge: '1d', immutable: false})
})
```### Options
#### `root` (required)
The absolute path of the directory containing the files to serve.
The file to serve is determined by combining `req.url` with the
root directory.An array of directories can be provided to serve multiple static directories
under a single prefix. Files are served in a "first found, first served" manner,
so list directories in order of priority. Duplicate paths will raise an error.#### `prefix`
Default: `'/'`
A URL path prefix used to create a virtual mount path for the static directory.
#### `constraints`
Default: `{}`
Constraints to add to registered routes. See Fastify's documentation for
[route constraints](https://fastify.dev/docs/latest/Reference/Routes/#constraints).#### `logLevel`
Default: `info`
Set log level for registered routes.
#### `prefixAvoidTrailingSlash`
Default: `false`
If `false`, the prefix gets a trailing "/". If `true`, no trailing "/" is added to the prefix.
#### `schemaHide`
Default: `true`
A flag that defines if the fastify route hide-schema attribute is hidden or not.
#### `setHeaders`
Default: `undefined`
A function to set custom headers on the response. Alterations to the headers
must be done synchronously. The function is called as `fn(res, path, stat)`,
with the arguments:- `res` The response object.
- `path` The path of the file that is being sent.
- `stat` The stat object of the file that is being sent.#### `send` Options
The following options are also supported and will be passed directly to the
[`@fastify/send`](https://www.npmjs.com/package/@fastify/send) module:- [`acceptRanges`](https://www.npmjs.com/package/@fastify/send#acceptranges)
- [`contentType`](https://www.npmjs.com/package/@fastify/send#contenttype)
- [`cacheControl`](https://www.npmjs.com/package/@fastify/send#cachecontrol) - Enable or disable setting Cache-Control response header (defaults to `true`). To provide a custom Cache-Control header, set this option to false
- [`dotfiles`](https://www.npmjs.com/package/@fastify/send#dotfiles)
- [`etag`](https://www.npmjs.com/package/@fastify/send#etag)
- [`extensions`](https://www.npmjs.com/package/@fastify/send#extensions)
- [`immutable`](https://www.npmjs.com/package/@fastify/send#immutable)
- [`index`](https://www.npmjs.com/package/@fastify/send#index)
- [`lastModified`](https://www.npmjs.com/package/@fastify/send#lastmodified)
- [`maxAge`](https://www.npmjs.com/package/@fastify/send#maxage)These options can be altered when calling `reply.sendFile('filename.html', options)` or `reply.sendFile('filename.html', 'otherfilename.html', options)` on each response.
#### `redirect`
Default: `false`
If set to `true`, `@fastify/static` redirects to the directory with a trailing slash.
This option cannot be `true` if `wildcard` is `false` and `ignoreTrailingSlash` is `true`.
If `false`, requesting directories without a trailing slash triggers the app's 404 handler using `reply.callNotFound()`.
#### `wildcard`
Default: `true`
If `true`, `@fastify/static` adds a wildcard route to serve files.
If `false`, it globs the filesystem for all defined files in the
served folder (`${root}/**/**`) and creates the necessary routes,
but will not serve newly added files.The default options of [`glob`](https://www.npmjs.com/package/glob)
are applied for getting the file list.This option cannot be `false` if `redirect` is `true` and `ignoreTrailingSlash` is `true`.
#### `allowedPath`
Default: `(pathName, root, request) => true`
This function filters served files. Using the request object, complex path authentication is possible.
Returning `true` serves the file; returning `false` calls Fastify's 404 handler.#### `index`
Default: `undefined`
Under the hood, [`@fastify/send`](https://www.npmjs.com/package/@fastify/send) supports "index.html" files by default.
To disable this, set `false`, or supply a new index by passing a string or an array in preferred order.#### `serveDotFiles`
Default: `false`
If `true`, serves files in hidden directories (e.g., `.foo`).
#### `list`
Default: `undefined`
If set, provides the directory list by calling the directory path.
Default response is JSON.Multi-root is not supported within the `list` option.
If `dotfiles` is `deny` or `ignore`, dotfiles are excluded.
Example:
```js
fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public'),
prefix: '/public/',
index: false
list: true
})
```Request
```bash
GET /public
```Response
```json
{ "dirs": ["dir1", "dir2"], "files": ["file1.png", "file2.txt"] }
```#### `list.format`
Default: `json`
Options: `html`, `json`
Directory list can be in `html` format; in that case, `list.render` function is required.
This option can be overridden by the URL parameter `format`. Options are `html` and `json`.
```bash
GET /public/assets?format=json
```Returns the response as JSON, regardless of `list.format`.
Example:
```js
fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'public'),
prefix: '/public/',
list: {
format: 'html',
render: (dirs, files) => {
return `
- ${dir.name} `).join('\n ')}
${dirs.map(dir => `
- ${file.name} `).join('\n ')}
${files.map(file => `
`
},
}
})
```
Request
```bash
GET /public
```
Response
```html
```
#### `list.names`
Default: `['']`
Directory list can respond to different routes declared in `list.names`.
> 🛈 Note: If a file with the same name exists, the actual file is sent.
Example:
```js
fastify.register(require('@fastify/static'), {
root: path.join(__dirname, '/static'),
prefix: '/public',
prefixAvoidTrailingSlash: true,
list: {
format: 'json',
names: ['index', 'index.json', '/']
}
})
```
Dir list respond with the same content to:
```bash
GET /public
GET /public/
GET /public/index
GET /public/index.json
```
#### `list.extendedFolderInfo`
Default: `undefined`
If `true`, extended information for folders will be accessible in `list.render` and the JSON response.
```js
render(dirs, files) {
const dir = dirs[0];
dir.fileCount // number of files in this folder
dir.totalFileCount // number of files in this folder (recursive)
dir.folderCount // number of folders in this folder
dir.totalFolderCount // number of folders in this folder (recursive)
dir.totalSize // size of all files in this folder (recursive)
dir.lastModified // most recent last modified timestamp of all files in this folder (recursive)
}
```
> âš Warning: This will slightly decrease the performance, especially for deeply nested file structures.
#### `list.jsonFormat`
Default: `names`
Options: `names`, `extended`
Determines the output format when `json` is selected.
`names`:
```json
{
"dirs": [
"dir1",
"dir2"
],
"files": [
"file1.txt",
"file2.txt"
]
}
```
`extended`:
```json
{
"dirs": [
{
"name": "dir1",
"stats": {
"dev": 2100,
"size": 4096
},
"extendedInfo": {
"fileCount": 4,
"totalSize": 51233
}
}
],
"files": [
{
"name": "file1.txt",
"stats": {
"dev": 2200,
"size": 554
}
}
]
}
```
#### `preCompressed`
Default: `false`
First, try to send the brotli encoded asset (if supported by `Accept-Encoding` headers), then gzip, and finally the original `pathname`. Skip compression for smaller files that do not benefit from it.
Assume this structure with the compressed asset as a sibling of the uncompressed counterpart:
```
./public
├── main.js
├── main.js.br
├── main.js.gz
├── crit.css
├── crit.css.gz
└── index.html
```
#### Disable serving
To use only the reply decorator without serving directories, pass `{ serve: false }`.
This prevents the plugin from serving everything under `root`.
#### Disabling reply decorator
The reply object is decorated with a `sendFile` function by default. To disable this,
pass `{ decorateReply: false }`. If `@fastify/static` is registered to multiple prefixes
in the same route, only one can initialize reply decorators.
#### Handling 404s
If a request matches the URL `prefix` but no file is found, Fastify's 404
handler is called. Set a custom 404 handler with [`fastify.setNotFoundHandler()`](https://fastify.dev/docs/latest/Reference/Server/#setnotfoundhandler).
When registering `@fastify/static` within an encapsulated context, the `wildcard` option may need to be set to `false` to support index resolution and nested not-found-handler:
```js
const app = require('fastify')();
app.register((childContext, _, done) => {
childContext.register(require('@fastify/static'), {
root: path.join(__dirname, 'docs'), // docs is a folder that contains `index.html` and `404.html`
wildcard: false
});
childContext.setNotFoundHandler((_, reply) => {
return reply.code(404).type('text/html').sendFile('404.html');
});
done();
}, { prefix: 'docs' });
```
This code will send the `index.html` for the paths `docs`, `docs/`, and `docs/index.html`. For all other `docs/` it will reply with `404.html`.
### Handling Errors
If an error occurs while sending a file, it is passed to Fastify's error handler.
Set a custom handler with [`fastify.setErrorHandler()`](https://fastify.dev/docs/latest/Reference/Server/#seterrorhandler).
### Payload `stream.path`
Access the file path inside the `onSend` hook using `payload.path`.
```js
fastify.addHook('onSend', function (req, reply, payload, next) {
console.log(payload.path)
next()
})
```
## License
Licensed under [MIT](./LICENSE).