Skip to content

fastify/fastify-static

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@fastify/static

CI NPM version neostandard javascript style

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 ^3.x
^2.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 for more details.

Usage

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

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

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 })
})

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.

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 module:

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 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 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:

fastify.register(require('@fastify/static'), {
  root: path.join(__dirname, 'public'),
  prefix: '/public/',
  index: false
  list: true
})

Request

GET /public

Response

{ "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.

GET /public/assets?format=json

Returns the response as JSON, regardless of list.format.

Example:

fastify.register(require('@fastify/static'), {
  root: path.join(__dirname, 'public'),
  prefix: '/public/',
  list: {
    format: 'html',
    render: (dirs, files) => {
      return `
<html><body>
<ul>
  ${dirs.map(dir => `<li><a href="${dir.href}">${dir.name}</a></li>`).join('\n  ')}
</ul>
<ul>
  ${files.map(file => `<li><a href="${file.href}" target="_blank">${file.name}</a></li>`).join('\n  ')}
</ul>
</body></html>
`
      },
  }
})

Request

GET /public

Response

<html><body>
<ul>
  <li><a href="/dir1">dir1</a></li>
  <li><a href="/dir1">dir2</a></li>
</ul>
<ul>
  <li><a href="/foo.html" target="_blank">foo.html</a></li>
  <li><a href="/foobar.html" target="_blank">foobar.html</a></li>
  <li><a href="/index.css" target="_blank">index.css</a></li>
  <li><a href="/index.html" target="_blank">index.html</a></li>
</ul>
</body></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:

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:

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.

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:

{
  "dirs": [
    "dir1",
    "dir2"
  ],
  "files": [
    "file1.txt",
    "file2.txt"
  ]
}

extended:

{
  "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().

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:

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/<undefined-routes> 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().

Payload stream.path

Access the file path inside the onSend hook using payload.path.

fastify.addHook('onSend', function (req, reply, payload, next) {
  console.log(payload.path)
  next()
})

License

Licensed under MIT.