{"id":13484014,"url":"https://github.com/fastify/fastify-static","last_synced_at":"2025-05-13T21:05:46.785Z","repository":{"id":24148894,"uuid":"100581886","full_name":"fastify/fastify-static","owner":"fastify","description":"Plugin for serving static files as fast as possible","archived":false,"fork":false,"pushed_at":"2025-03-30T21:32:41.000Z","size":626,"stargazers_count":463,"open_issues_count":12,"forks_count":106,"subscribers_count":18,"default_branch":"main","last_synced_at":"2025-04-25T09:07:41.561Z","etag":null,"topics":["fastify","fastify-plugin","file-server","static"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/@fastify/static","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fastify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"fastify","open_collective":"fastify"}},"created_at":"2017-08-17T08:43:43.000Z","updated_at":"2025-04-10T17:26:39.000Z","dependencies_parsed_at":"2024-03-04T13:56:49.002Z","dependency_job_id":"c0bf5cc7-5c2c-4acf-83e6-7e0d301cd9dc","html_url":"https://github.com/fastify/fastify-static","commit_stats":{"total_commits":401,"total_committers":82,"mean_commits":4.890243902439025,"dds":0.7905236907730673,"last_synced_commit":"dd5966d54bf2e8bf23d7f7c1bcf8596962f5a866"},"previous_names":[],"tags_count":89,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-static","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-static/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-static/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Ffastify-static/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fastify","download_url":"https://codeload.github.com/fastify/fastify-static/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250928080,"owners_count":21509315,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","fastify-plugin","file-server","static"],"created_at":"2024-07-31T17:01:17.997Z","updated_at":"2025-04-28T12:13:31.885Z","avatar_url":"https://github.com/fastify.png","language":"JavaScript","readme":"# @fastify/static\n\n[![CI](https://github.com/fastify/fastify-static/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-static/actions/workflows/ci.yml)\n[![NPM version](https://img.shields.io/npm/v/@fastify/static.svg?style=flat)](https://www.npmjs.com/package/@fastify/static)\n[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)\n\nPlugin for serving static files as fast as possible.\n\n## Install\n```\nnpm i @fastify/static\n```\n\n### Compatibility\n\n| Plugin version | Fastify version |\n| ---------------|-----------------|\n| `\u003e=8.x`        | `^5.x`          |\n| `^7.x`         | `^4.x`          |\n| `\u003e=5.x \u003c7.x`   | `^3.x`          |\n| `\u003e=2.x \u003c5.x`   | `^2.x`          |\n| `^1.x`         | `^1.x`          |\n\n\nPlease note that if a Fastify version is out of support, then so are the corresponding versions of this plugin\nin the table above.\nSee [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details.\n\n## Usage\n\n```js\nconst fastify = require('fastify')({logger: true})\nconst path = require('node:path')\n\nfastify.register(require('@fastify/static'), {\n  root: path.join(__dirname, 'public'),\n  prefix: '/public/', // optional: default '/'\n  constraints: { host: 'example.com' } // optional: default {}\n})\n\nfastify.get('/another/path', function (req, reply) {\n  reply.sendFile('myHtml.html') // serving path.join(__dirname, 'public', 'myHtml.html') directly\n})\n\nfastify.get('/another/patch-async', async function (req, reply) {\n  return reply.sendFile('myHtml.html')\n})\n\nfastify.get('/path/with/different/root', function (req, reply) {\n  reply.sendFile('myHtml.html', path.join(__dirname, 'build')) // serving a file from a different root location\n})\n\nfastify.get('/another/path', function (req, reply) {\n  reply.sendFile('myHtml.html', { cacheControl: false }) // overriding the options disabling cache-control headers\n})\n\n// Run the server!\nfastify.listen({ port: 3000 }, (err, address) =\u003e {\n  if (err) throw err\n  // Server is now listening on ${address}\n})\n```\n\n### Multiple prefixed roots\n\n```js\nconst fastify = require('fastify')()\nconst fastifyStatic = require('@fastify/static')\nconst path = require('node:path')\n// first plugin\nfastify.register(fastifyStatic, {\n  root: path.join(__dirname, 'public')\n})\n\n// second plugin\nfastify.register(fastifyStatic, {\n  root: path.join(__dirname, 'node_modules'),\n  prefix: '/node_modules/',\n  decorateReply: false // the reply decorator has been added by the first plugin registration\n})\n\n```\n\n### Sending a file with `content-disposition` header\n\n```js\nconst fastify = require('fastify')()\nconst path = require('node:path')\n\nfastify.register(require('@fastify/static'), {\n  root: path.join(__dirname, 'public'),\n  prefix: '/public/', // optional: default '/'\n})\n\nfastify.get('/another/path', function (req, reply) {\n  reply.download('myHtml.html', 'custom-filename.html') // sending path.join(__dirname, 'public', 'myHtml.html') directly with custom filename\n})\n\nfastify.get('another/patch-async', async function (req, reply) {\n  // an async handler must always return the reply object\n  return reply.download('myHtml.html', 'custom-filename.html')\n})\n\nfastify.get('/path/without/cache/control', function (req, reply) {\n  reply.download('myHtml.html', { cacheControl: false }) // serving a file disabling cache-control headers\n})\n\nfastify.get('/path/without/cache/control', function (req, reply) {\n  reply.download('myHtml.html', 'custom-filename.html', { cacheControl: false })\n})\n\n```\n\n### Managing cache-control headers\n\nProduction sites should use a reverse-proxy to manage caching headers.\nHowever, 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.\n\n```js\nfastify.register(require('@fastify/static'), {\n  root: path.join(import.meta.dirname, 'dist'), // import.meta.dirname node.js \u003e= v20.11.0\n  // By default all assets are immutable and can be cached for a long period due to cache bursting techniques\n  maxAge: '30d',\n  immutable: true,\n})\n\n// Explicitly reduce caching of assets that don't use cache bursting techniques\nfastify.get('/', function (req, reply) {\n  // index.html should never be cached\n  reply.sendFile('index.html', {maxAge: 0, immutable: false})\n})\n\nfastify.get('/favicon.ico', function (req, reply) {\n  // favicon can be cached for a short period\n  reply.sendFile('favicon.ico', {maxAge: '1d', immutable: false})\n})\n```\n\n### Options\n\n#### `root` (required)\n\nThe absolute path of the directory containing the files to serve.\nThe file to serve is determined by combining `req.url` with the\nroot directory.\n\nAn array of directories can be provided to serve multiple static directories\nunder a single prefix. Files are served in a \"first found, first served\" manner,\nso list directories in order of priority. Duplicate paths will raise an error.\n\n#### `prefix`\n\nDefault: `'/'`\n\nA URL path prefix used to create a virtual mount path for the static directory.\n\n#### `constraints`\n\nDefault: `{}`\n\nConstraints to add to registered routes. See Fastify's documentation for\n[route constraints](https://fastify.dev/docs/latest/Reference/Routes/#constraints).\n\n#### `logLevel`\n\nDefault: `info`\n\nSet log level for registered routes.\n\n#### `prefixAvoidTrailingSlash`\n\nDefault: `false`\n\nIf `false`, the prefix gets a trailing \"/\". If `true`, no trailing \"/\" is added to the prefix.\n\n#### `schemaHide`\n\nDefault: `true`\n\nA flag that defines if the fastify route hide-schema attribute is hidden or not.\n\n#### `setHeaders`\n\nDefault: `undefined`\n\nA function to set custom headers on the response. Alterations to the headers\nmust be done synchronously. The function is called as `fn(res, path, stat)`,\nwith the arguments:\n\n- `res` The response object.\n- `path` The path of the file that is being sent.\n- `stat` The stat object of the file that is being sent.\n\n#### `send` Options\n\nThe following options are also supported and will be passed directly to the\n[`@fastify/send`](https://www.npmjs.com/package/@fastify/send) module:\n\n- [`acceptRanges`](https://www.npmjs.com/package/@fastify/send#acceptranges)\n- [`contentType`](https://www.npmjs.com/package/@fastify/send#contenttype)\n- [`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\n- [`dotfiles`](https://www.npmjs.com/package/@fastify/send#dotfiles)\n- [`etag`](https://www.npmjs.com/package/@fastify/send#etag)\n- [`extensions`](https://www.npmjs.com/package/@fastify/send#extensions)\n- [`immutable`](https://www.npmjs.com/package/@fastify/send#immutable)\n- [`index`](https://www.npmjs.com/package/@fastify/send#index)\n- [`lastModified`](https://www.npmjs.com/package/@fastify/send#lastmodified)\n- [`maxAge`](https://www.npmjs.com/package/@fastify/send#maxage)\n\nThese options can be altered when calling `reply.sendFile('filename.html', options)` or `reply.sendFile('filename.html', 'otherfilename.html', options)` on each response.\n\n#### `redirect`\n\nDefault: `false`\n\nIf set to `true`, `@fastify/static` redirects to the directory with a trailing slash.\n\nThis option cannot be `true` if `wildcard` is `false` and `ignoreTrailingSlash` is `true`.\n\nIf `false`, requesting directories without a trailing slash triggers the app's 404 handler using `reply.callNotFound()`.\n\n#### `wildcard`\n\nDefault: `true`\n\nIf `true`, `@fastify/static` adds a wildcard route to serve files.\nIf `false`, it globs the filesystem for all defined files in the\nserved folder (`${root}/**/**`) and creates the necessary routes,\nbut will not serve newly added files.\n\nThe default options of [`glob`](https://www.npmjs.com/package/glob)\nare applied for getting the file list.\n\nThis option cannot be `false` if `redirect` is `true` and `ignoreTrailingSlash` is `true`.\n\n#### `allowedPath`\n\nDefault: `(pathName, root, request) =\u003e true`\n\nThis function filters served files. Using the request object, complex path authentication is possible.\nReturning `true` serves the file; returning `false` calls Fastify's 404 handler.\n\n#### `index`\n\nDefault: `undefined`\n\nUnder the hood, [`@fastify/send`](https://www.npmjs.com/package/@fastify/send) supports \"index.html\" files by default.\nTo disable this, set `false`, or supply a new index by passing a string or an array in preferred order.\n\n#### `serveDotFiles`\n\nDefault: `false`\n\nIf `true`, serves files in hidden directories (e.g., `.foo`).\n\n#### `list`\n\nDefault: `undefined`\n\nIf set, provides the directory list by calling the directory path.\nDefault response is JSON.\n\nMulti-root is not supported within the `list` option.\n\nIf `dotfiles` is `deny` or `ignore`, dotfiles are excluded.\n\nExample:\n\n```js\nfastify.register(require('@fastify/static'), {\n  root: path.join(__dirname, 'public'),\n  prefix: '/public/',\n  index: false\n  list: true\n})\n```\n\nRequest\n\n```bash\nGET /public\n```\n\nResponse\n\n```json\n{ \"dirs\": [\"dir1\", \"dir2\"], \"files\": [\"file1.png\", \"file2.txt\"] }\n```\n\n#### `list.format`\n\nDefault: `json`\n\nOptions: `html`, `json`\n\nDirectory list can be in `html` format; in that case, `list.render` function is required.\n\nThis option can be overridden by the URL parameter `format`. Options are `html` and `json`.\n\n```bash\nGET /public/assets?format=json\n```\n\nReturns the response as JSON, regardless of `list.format`.\n\nExample:\n\n```js\nfastify.register(require('@fastify/static'), {\n  root: path.join(__dirname, 'public'),\n  prefix: '/public/',\n  list: {\n    format: 'html',\n    render: (dirs, files) =\u003e {\n      return `\n\u003chtml\u003e\u003cbody\u003e\n\u003cul\u003e\n  ${dirs.map(dir =\u003e `\u003cli\u003e\u003ca href=\"${dir.href}\"\u003e${dir.name}\u003c/a\u003e\u003c/li\u003e`).join('\\n  ')}\n\u003c/ul\u003e\n\u003cul\u003e\n  ${files.map(file =\u003e `\u003cli\u003e\u003ca href=\"${file.href}\" target=\"_blank\"\u003e${file.name}\u003c/a\u003e\u003c/li\u003e`).join('\\n  ')}\n\u003c/ul\u003e\n\u003c/body\u003e\u003c/html\u003e\n`\n      },\n  }\n})\n```\n\nRequest\n\n```bash\nGET /public\n```\n\nResponse\n\n```html\n\u003chtml\u003e\u003cbody\u003e\n\u003cul\u003e\n  \u003cli\u003e\u003ca href=\"/dir1\"\u003edir1\u003c/a\u003e\u003c/li\u003e\n  \u003cli\u003e\u003ca href=\"/dir1\"\u003edir2\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cul\u003e\n  \u003cli\u003e\u003ca href=\"/foo.html\" target=\"_blank\"\u003efoo.html\u003c/a\u003e\u003c/li\u003e\n  \u003cli\u003e\u003ca href=\"/foobar.html\" target=\"_blank\"\u003efoobar.html\u003c/a\u003e\u003c/li\u003e\n  \u003cli\u003e\u003ca href=\"/index.css\" target=\"_blank\"\u003eindex.css\u003c/a\u003e\u003c/li\u003e\n  \u003cli\u003e\u003ca href=\"/index.html\" target=\"_blank\"\u003eindex.html\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\n#### `list.names`\n\nDefault: `['']`\n\nDirectory list can respond to different routes declared in `list.names`.\n\n\u003e 🛈 Note: If a file with the same name exists, the actual file is sent.\n\nExample:\n\n```js\nfastify.register(require('@fastify/static'), {\n  root: path.join(__dirname, '/static'),\n  prefix: '/public',\n  prefixAvoidTrailingSlash: true,\n  list: {\n    format: 'json',\n    names: ['index', 'index.json', '/']\n  }\n})\n```\n\nDir list respond with the same content to:\n\n```bash\nGET /public\nGET /public/\nGET /public/index\nGET /public/index.json\n```\n\n#### `list.extendedFolderInfo`\n\nDefault: `undefined`\n\nIf `true`, extended information for folders will be accessible in `list.render` and the JSON response.\n\n```js\nrender(dirs, files) {\n  const dir = dirs[0];\n  dir.fileCount // number of files in this folder\n  dir.totalFileCount // number of files in this folder (recursive)\n  dir.folderCount // number of folders in this folder\n  dir.totalFolderCount // number of folders in this folder (recursive)\n  dir.totalSize // size of all files in this folder (recursive)\n  dir.lastModified // most recent last modified timestamp of all files in this folder (recursive)\n}\n```\n\n\u003e ⚠ Warning: This will slightly decrease the performance, especially for deeply nested file structures.\n\n#### `list.jsonFormat`\n\nDefault: `names`\n\nOptions: `names`, `extended`\n\nDetermines the output format when `json` is selected.\n\n`names`:\n```json\n{\n  \"dirs\": [\n    \"dir1\",\n    \"dir2\"\n  ],\n  \"files\": [\n    \"file1.txt\",\n    \"file2.txt\"\n  ]\n}\n```\n\n`extended`:\n```json\n{\n  \"dirs\": [\n    {\n      \"name\": \"dir1\",\n      \"stats\": {\n        \"dev\": 2100,\n        \"size\": 4096\n      },\n      \"extendedInfo\": {\n        \"fileCount\": 4,\n        \"totalSize\": 51233\n      }\n    }\n  ],\n  \"files\": [\n    {\n      \"name\": \"file1.txt\",\n      \"stats\": {\n        \"dev\": 2200,\n        \"size\": 554\n      }\n    }\n  ]\n}\n```\n\n#### `preCompressed`\n\nDefault: `false`\n\nFirst, 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.\n\nAssume this structure with the compressed asset as a sibling of the uncompressed counterpart:\n\n```\n./public\n├── main.js\n├── main.js.br\n├── main.js.gz\n├── crit.css\n├── crit.css.gz\n└── index.html\n```\n\n#### Disable serving\n\nTo use only the reply decorator without serving directories, pass `{ serve: false }`.\nThis prevents the plugin from serving everything under `root`.\n\n#### Disabling reply decorator\n\nThe reply object is decorated with a `sendFile` function by default. To disable this,\npass `{ decorateReply: false }`. If `@fastify/static` is registered to multiple prefixes\nin the same route, only one can initialize reply decorators.\n\n#### Handling 404s\n\nIf a request matches the URL `prefix` but no file is found, Fastify's 404\nhandler is called. Set a custom 404 handler with [`fastify.setNotFoundHandler()`](https://fastify.dev/docs/latest/Reference/Server/#setnotfoundhandler).\n\nWhen 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:\n\n```js\nconst app = require('fastify')();\n\napp.register((childContext, _, done) =\u003e {\n    childContext.register(require('@fastify/static'), {\n        root: path.join(__dirname, 'docs'), // docs is a folder that contains `index.html` and `404.html`\n        wildcard: false\n    });\n    childContext.setNotFoundHandler((_, reply) =\u003e {\n        return reply.code(404).type('text/html').sendFile('404.html');\n    });\n    done();\n}, { prefix: 'docs' });\n```\n\nThis code will send the `index.html` for the paths `docs`, `docs/`, and `docs/index.html`. For all other `docs/\u003cundefined-routes\u003e` it will reply with `404.html`.\n\n### Handling Errors\n\nIf an error occurs while sending a file, it is passed to Fastify's error handler.\nSet a custom handler with [`fastify.setErrorHandler()`](https://fastify.dev/docs/latest/Reference/Server/#seterrorhandler).\n\n### Payload `stream.path`\n\nAccess the file path inside the `onSend` hook using `payload.path`.\n\n```js\nfastify.addHook('onSend', function (req, reply, payload, next) {\n  console.log(payload.path)\n  next()\n})\n```\n\n## License\n\nLicensed under [MIT](./LICENSE).\n","funding_links":["https://github.com/sponsors/fastify","https://opencollective.com/fastify"],"categories":["JavaScript","\u003ch2 align=\"center\"\u003eAwesome Fastify\u003c/h2\u003e"],"sub_categories":["\u003ch2 align=\"center\"\u003eEcosystem\u003c/h2\u003e"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Ffastify-static","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffastify%2Ffastify-static","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Ffastify-static/lists"}