### Development Server Setup with Inert
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Configure a Hapi.js server for development using Inert. This setup includes a smaller ETags cache and enables directory listing for easier local testing.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: {
files: {
relativeTo: __dirname
}
},
plugins: {
inert: {
etagsCacheMaxSize: 1000 // Smaller cache in dev
}
}
});
await server.register(Inert);
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true,
etagMethod: 'simple' // Faster for dev
}
}
});
```
--------------------------------
### Example HTML Directory Listing Output
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
This is an example of the auto-generated HTML output for a directory listing, including file links and parent directory navigation.
```html
Directory: /downloads/subfolder/
Directory: /downloads/subfolder/
```
--------------------------------
### Pre-Compressed Files Setup
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Configures the file handler to look for and serve pre-compressed versions of files based on the client's Accept-Encoding header.
```javascript
handler: {
file: {
path: './app.js',
lookupCompressed: true,
lookupMap: {
gzip: '.gz',
brotli: '.br'
}
}
}
```
--------------------------------
### Example: Requesting a Non-Existent File
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Demonstrates how to make a request to a file that does not exist and checks the resulting status code and error message.
```javascript
// Request to non-existent file
const res = await server.inject({
method: 'GET',
url: '/files/does-not-exist.pdf'
});
// res.statusCode === 404
// res.result.statusCode === 404
// res.result.message === 'Not Found'
```
--------------------------------
### Byte Range Options Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Specifies byte ranges for serving a portion of a file, useful for large media files.
```javascript
handler: {
file: {
path: './large-video.mp4',
start: 1000000, // Skip first 1MB
end: 2000000 // Stop after 2MB total
}
}
```
--------------------------------
### Basic Static File Server Setup
Source: https://github.com/hapijs/inert/blob/master/API.md
Sets up a Hapi server to serve static files from a 'public' directory. Requires registering the Inert plugin.
```javascript
const Path = require('path');
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: {
files: {
relativeTo: Path.join(__dirname, 'public')
}
}
});
const provision = async () => {
await server.register(Inert);
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: '.',
redirectToSlash: true,
index: true,
}
}
});
await server.start();
console.log('Server running at:', server.info.uri);
};
provision();
```
--------------------------------
### ReplyFileHandlerOptions Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Example of configuring options for the reply.file() method to serve a PDF with custom download settings.
```javascript
const handler = (request, h) => {
return h.file('./reports/report.pdf', {
mode: 'attachment',
filename: `report-${request.params.id}.pdf`,
etagMethod: 'simple'
});
};
server.route({
method: 'GET',
path: '/report/{id}',
config: {
files: { relativeTo: __dirname }
},
handler
});
```
--------------------------------
### Directory Handler Route Definition Examples
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Examples of how to define routes using the directory handler with different path matching strategies.
```APIDOC
## GET /public/{param*}
### Description
Serves static files from the './public' directory, matching any depth of path.
### Method
GET
### Endpoint
/public/{param*}
### Parameters
#### Path Parameters
- **param*** (string) - Required - Captures the requested file/path at any depth.
### Request Example
```json
{
"example": "No request body for GET requests"
}
```
### Response
#### Success Response (200)
- **file content** (binary) - The content of the requested static file.
#### Response Example
```json
{
"example": "[Binary file content]"
}
```
## GET /downloads/{file}
### Description
Serves static files from the './downloads' directory, matching only a single level path.
### Method
GET
### Endpoint
/downloads/{file}
### Parameters
#### Path Parameters
- **file** (string) - Required - Captures the requested file name.
### Request Example
```json
{
"example": "No request body for GET requests"
}
```
### Response
#### Success Response (200)
- **file content** (binary) - The content of the requested static file.
#### Response Example
```json
{
"example": "[Binary file content]"
}
```
## GET /files/{path*3}
### Description
Serves static files from the './files' directory, matching exactly 3 levels of path.
### Method
GET
### Endpoint
/files/{path*3}
### Parameters
#### Path Parameters
- **path*3** (string) - Required - Captures the requested file/path up to 3 levels deep.
### Request Example
```json
{
"example": "No request body for GET requests"
}
```
### Response
#### Success Response (200)
- **file content** (binary) - The content of the requested static file.
#### Response Example
```json
{
"example": "[Binary file content]"
}
```
```
--------------------------------
### File Handler Configuration Validation Examples
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Illustrates common validation errors for the file handler configuration. Ensure 'path' is provided, 'filename' includes 'mode', 'start' is not greater than 'end', and 'etagMethod' is valid.
```javascript
// Path is required
handler: { file: { /* missing path */ } } // Error
```
```javascript
// Filename requires mode
handler: { file: { path: './', filename: 'test.pdf' } } // Error
```
```javascript
// start must be <= end
handler: { file: { path: './', start: 100, end: 50 } } // Error
```
```javascript
// etagMethod must be 'hash', 'simple', or false
handler: { file: { path: './', etagMethod: 'invalid' } } // Error
```
--------------------------------
### Production Server Setup with Inert
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Configure a Hapi.js server for production using Inert. This setup uses a larger ETags cache, enables compression for responses over 1KB, and uses SHA1 ETags for distributed deployments.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: {
files: {
relativeTo: __dirname
}
},
compression: {
minBytes: 1024
},
plugins: {
inert: {
etagsCacheMaxSize: 50000 // Large cache for production
}
}
});
await server.register(Inert);
server.route({
method: 'GET',
path: '/api/static/{path*}',
handler: {
directory: {
path: './dist',
index: ['index.html'],
listing: false,
lookupCompressed: true,
etagMethod: 'hash' // SHA1 for distributed deployment
}
}
});
```
--------------------------------
### Example of Simple ETag Computation
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/etag.md
Provides an example of the resulting ETag string based on specific file size and modification time values.
```javascript
// File: 4096 bytes, modified at 1686000000000ms
// ETag: "1000-1896db26e8"
```
--------------------------------
### Install @hapi/inert Plugin
Source: https://github.com/hapijs/inert/blob/master/_autodocs/README.md
Install the @hapi/inert plugin using npm. This command downloads and installs the package into your project's node_modules directory.
```bash
npm install @hapi/inert
```
--------------------------------
### HTTP 206 Partial Content Request Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Demonstrates a client request for a byte range and the corresponding server response for partial content.
```http
GET /file HTTP/1.1
Range: bytes=0-1023
```
```http
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/5000
Content-Length: 1024
```
--------------------------------
### Registering Inert and Serving Files/Directories
Source: https://github.com/hapijs/inert/blob/master/_autodocs/START_HERE.md
This example demonstrates how to register the @hapi/inert plugin and configure routes to serve a single file as an attachment and a directory of files with directory listing enabled.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: { files: { relativeTo: __dirname } }
});
// Register plugin
await server.register(Inert);
// Serve single file
server.route({
method: 'GET',
path: '/download',
handler: {
file: {
path: './files/report.pdf',
mode: 'attachment' // Forces download
}
}
});
// Serve directory
server.route({
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true
}
}
});
await server.start();
```
--------------------------------
### Hapi Server Setup with Inert Plugin
Source: https://github.com/hapijs/inert/blob/master/_autodocs/README.md
Register the @hapi/inert plugin with a Hapi server and configure routes for serving static files and directories. Ensure routes have file access configured relative to __dirname.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: { files: { relativeTo: __dirname } }
});
await server.register(Inert);
// Serve single file
server.route({
method: 'GET',
path: '/download/{filename}',
handler: {
file: {
path: (request) => `./downloads/${request.params.filename}`,
mode: 'attachment'
}
}
});
// Serve directory
server.route({
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true
}
}
});
await server.start();
```
--------------------------------
### Example: Requesting a Directory with File Handler
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Demonstrates configuring a route to use the file handler with a path that is actually a directory, triggering a 403 error.
```javascript
// Request to directory with file handler
server.route({
method: 'GET',
path: '/file',
handler: {
file: './public' // Path is a directory!
}
});
const res = await server.inject('/file');
// res.statusCode === 403
// res.result.data.code === 'EISDIR'
```
--------------------------------
### Invalid File Handler: End Less Than Start
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This example demonstrates an invalid configuration for the file handler where 'end' is less than 'start', resulting in a validation error.
```javascript
handler: {
file: {
path: './',
start: 100,
end: 50 // Error: end must be >= start
}
}
```
--------------------------------
### Serve a Dynamic File Based on Request Parameters
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
This example shows how to dynamically determine the file path to serve based on request parameters. The `relativeTo` option in `config.files` is useful for resolving paths relative to the current file.
```javascript
server.route({
method: 'GET',
path: '/user/{id}/download',
config: {
files: { relativeTo: __dirname }
},
handler: {
file: (request) => {
return `./user-data/${request.params.id}/export.pdf`;
}
}
});
```
--------------------------------
### Path Confinement Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
Demonstrates how to use the 'confine' option in the file handler to prevent directory traversal. The resolved path must be within the specified confine directory.
```javascript
handler: {
file: {
path: '../../etc/passwd', // Blocked!
confine: './public'
}
}
```
--------------------------------
### Toolkit Method: reply.file() Usage Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Use the `reply.file()` toolkit method within a route handler to serve a file with specified options like mode, filename, and etagMethod.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const Path = require('path');
const server = new Hapi.Server({ port: 3000 });
await server.register(Inert);
server.route({
method: 'GET',
path: '/resume',
config: {
files: { relativeTo: __dirname }
},
handler: (request, h) => {
return h.file('resumes/my-resume.pdf', {
mode: 'attachment',
filename: 'resume-2024.pdf',
etagMethod: 'hash'
});
}
});
await server.start();
```
--------------------------------
### Register Inert Plugin with Server Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/index.md
Example of registering the Inert plugin with custom ETag cache size configuration.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
plugins: {
inert: {
etagsCacheMaxSize: 5000 // Custom ETag cache size
}
}
});
await server.register(Inert);
// Now define routes using file and directory handlers
server.route({
method: 'GET',
path: '/static/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true
}
}
});
```
--------------------------------
### Memory-Constrained Server Setup with Inert
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Configure a Hapi.js server with Inert for memory-constrained environments. This setup disables ETags caching entirely and uses simple ETags to minimize memory usage.
```javascript
const server = new Hapi.Server({
port: 3000,
plugins: {
inert: {
etagsCacheMaxSize: 0 // Disable caching
}
}
});
await server.register(Inert);
// Use simple ETags (no caching needed)
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: './public',
etagMethod: 'simple' // Fast, no cache
}
}
});
```
--------------------------------
### Static Website Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/INDEX.md
Configure Inert to serve a static website. This setup uses a file handler for the main index file and a directory handler for other assets.
```plaintext
File Handler: ./public/index.html
Directory Handler: ./public/* with index: true
```
--------------------------------
### DirectoryHandlerRouteObject Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Example of configuring the directory handler to serve static files from a 'public' directory with specific settings.
```javascript
server.route({
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
index: ['index.html', 'default.html'],
listing: true,
showHidden: false,
redirectToSlash: true,
lookupCompressed: true,
etagMethod: 'hash'
}
}
});
```
--------------------------------
### Configure Server Compression for Inert
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Set server-level compression options to influence how Inert handles compressed files and response compression. This example sets a minimum byte size for compression.
```javascript
const server = new Hapi.Server({
compression: {
minBytes: 1024 // Only compress responses > 1KB
}
});
```
--------------------------------
### OptionalRegistrationOptions Usage Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Shows how to configure the inert plugin during server registration to limit the ETag cache size. Setting etagsCacheMaxSize to 0 disables ETag caching.
```javascript
const server = new Hapi.Server({
port: 3000,
plugins: {
inert: {
etagsCacheMaxSize: 5000 // Store up to 5000 ETags
}
}
});
await server.register(Inert);
```
--------------------------------
### File Handler Route Definition Examples
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Demonstrates three ways to configure the file handler in a Hapi route: using a simple string path, a function that returns a path, or a configuration object with advanced options.
```APIDOC
## GET /logo
### Description
Serves a static file located at a predefined path.
### Method
GET
### Endpoint
/logo
### Parameters
#### Request Body
This endpoint does not accept a request body.
### Request Example
This endpoint does not require a request body.
### Response
#### Success Response (200)
Serves the file specified in the handler configuration.
#### Response Example
(Binary content of the file)
---
## GET /user/{id}/avatar
### Description
Serves a user-specific avatar file where the path is determined dynamically by a function.
### Method
GET
### Endpoint
/user/{id}/avatar
### Parameters
#### Path Parameters
- **id** (string) - Required - The ID of the user whose avatar is to be served.
#### Request Body
This endpoint does not accept a request body.
### Request Example
This endpoint does not require a request body.
### Response
#### Success Response (200)
Serves the user's avatar file.
#### Response Example
(Binary content of the avatar file)
---
## GET /download/{filename}
### Description
Serves a file from the download directory with options for custom filename, content disposition, and confinement.
### Method
GET
### Endpoint
/download/{filename}
### Parameters
#### Path Parameters
- **filename** (string) - Required - The name of the file to download.
#### Request Body
This endpoint does not accept a request body.
### Request Example
This endpoint does not require a request body.
### Response
#### Success Response (200)
Serves the specified file, potentially with a custom filename and as an attachment.
#### Response Example
(Binary content of the file, potentially named 'custom-name.pdf')
```
--------------------------------
### File Handler with Simple ETags to Avoid Hashing Errors
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This example configures the file handler to use 'simple' ETags, which avoids potential errors associated with ETag hash computation.
```javascript
handler: {
file: {
path: './volatile.txt',
etagMethod: 'simple' // No hashing, no errors
}
}
```
--------------------------------
### Example: Requesting a Protected File
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Demonstrates making a request to a file that the process does not have permission to access and verifies the resulting 403 Forbidden status code.
```javascript
// Request to protected file
const res = await server.inject({
method: 'GET',
url: '/files/root-only.txt'
});
// res.statusCode === 403
// res.result.message === 'Forbidden'
```
--------------------------------
### RequestHandler Usage Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Demonstrates how to use the RequestHandler type to dynamically resolve a file path based on request parameters. This handler is then used within a server route configuration.
```javascript
const pathResolver: RequestHandler = (request) => {
return `/files/${request.params.userId}/document.pdf`;
};
server.route({
method: 'GET',
path: '/user/{userId}/files',
handler: {
file: pathResolver
}
});
```
--------------------------------
### FileHandlerRouteObject Usage Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Illustrates configuring the file handler with dynamic path resolution, attachment mode, confinement to a specific directory, and ETag hashing. Relative paths are resolved from the route's files.relativeTo setting.
```javascript
server.route({
method: 'GET',
path: '/download/{filename}',
config: {
files: { relativeTo: __dirname }
},
handler: {
file: {
path: (request) => `./downloads/${request.params.filename}`,
mode: 'attachment',
confine: './downloads',
etagMethod: 'hash'
}
}
});
```
--------------------------------
### Error Data Example for Permission Denied
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Shows the error data structure for permission denied errors, including the path and the specific file system error code (e.g., 'EACCES' or 'EPERM').
```javascript
{
path: '/path/to/protected/file.txt',
code: 'EACCES' // or 'EPERM'
}
```
--------------------------------
### API Static Assets Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/INDEX.md
Configure Inert to serve static assets for an API. This setup uses a directory handler with an index file and pre-compressed assets, suitable for distributed deployments.
```plaintext
Directory Handler: ./dist
Index: ['index.html']
Pre-compressed: true
ETag: 'hash' (distributed deployment)
```
--------------------------------
### Directory Handler Request Path Processing Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Illustrates how the directory handler resolves request paths by appending them to the base directory path defined in the route handler. Security measures like blocking absolute paths and path traversal are enforced.
```javascript
// Route: GET /files/{path*}
// Handler path: './downloads'
Request: GET /files/docs/readme.txt
Resolved: ./downloads/docs/readme.txt
Request: GET /files/subfolder/
Resolved: ./downloads/subfolder/
```
--------------------------------
### Basic hapi.js Server with Inert
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
Register the Inert plugin and configure routes to serve a single file and a directory. Ensure routes.files.relativeTo is set for relative path resolution.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
routes: {
files: { relativeTo: __dirname }
}
});
// Register the plugin
await server.register(Inert);
// Serve a single file
server.route({
method: 'GET',
path: '/style.css',
handler: { file: './public/style.css' }
});
// Serve a directory
server.route({
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true
}
}
});
await server.start();
```
--------------------------------
### Serve a Simple Static File
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Use this configuration to serve a static file directly from the file system. Ensure the file path is correct relative to the server's working directory.
```javascript
server.route({
method: 'GET',
path: '/style.css',
handler: {
file: './public/style.css'
}
});
```
--------------------------------
### Use Toolkit Method for File Serving with Options
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
This snippet demonstrates using the `h.file()` toolkit method within a handler to serve a file. It allows for specifying options like `mode` and `etagMethod` directly in the handler function.
```javascript
server.route({
method: 'GET',
path: '/api/export',
config: {
files: { relativeTo: __dirname }
},
handler: (request, h) => {
const reportFile = request.query.format === 'csv'
? './exports/report.csv'
: './exports/report.xlsx';
return h.file(reportFile, {
mode: 'attachment',
etagMethod: 'simple'
});
}
});
```
--------------------------------
### Get File Stats
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Retrieves statistics for an open file descriptor, including its size. This function wraps Node.js's fs.fstat().
```javascript
const fd = await exports.open('./file.txt', 'r');
const stats = await exports.fstat(fd);
console.log(`Size: ${stats.size}`);
await exports.close(fd);
```
--------------------------------
### Multiple Custom Index Files Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Configure the directory handler to try multiple custom index files in order and serve the first one that exists.
```javascript
handler: {
directory: {
index: ['index.html', 'index.htm', 'default.html']
}
}
```
--------------------------------
### Path Attack Prevention Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
The confine security feature prevents directory traversal attacks by validating resolved paths against the confine directory.
```javascript
// These are blocked when confine is './public'
'../../../etc/passwd' // Escapes confine
'./public-secret/file' // Sibling directory with same prefix
```
--------------------------------
### Module Architecture Overview
Source: https://github.com/hapijs/inert/blob/master/_autodocs/README.md
Illustrates the internal structure of the @hapi/inert module, showing the purpose of key files.
```text
@hapi/inert
├── lib/index.js → Plugin registration
├── lib/file.js → File handler
├── lib/directory.js → Directory handler
├── lib/etag.js → ETag generation & caching
└── lib/fs.js → File system wrapper
```
--------------------------------
### Basic Static Site Serving
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Serve static files from a specified directory. Ensure the 'public' directory exists and contains your site's assets. The 'index: true' option serves an index file (e.g., index.html) if present.
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({ port: 3000 });
await server.register(Inert);
server.route({
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: './public',
index: true
}
}
});
await server.start();
```
--------------------------------
### Handle Failed File Hash Error
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This example demonstrates handling a 500 error during ETag hash computation, which can happen if the file is deleted while hashing.
```javascript
// File deleted during ETag hash computation:
handler: {
file: {
path: './temp-file.txt',
etagMethod: 'hash' // Compute hash
}
}
// If file deleted during hash:
// res.statusCode === 500
// res.result.message === 'Failed to hash file'
```
--------------------------------
### Error Data Example for Is Directory Error
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Shows the error data for when the file handler is used on a path that is a directory, including the path and the 'EISDIR' code.
```javascript
{
path: '/path/to/directory',
code: 'EISDIR'
}
```
--------------------------------
### Enable Pre-Compressed Files Lookup
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Configure the directory handler to look for pre-compressed versions of files (e.g., '.gz', '.br') based on the client's 'Accept-Encoding' header. This optimizes delivery of static assets.
```javascript
handler: {
directory: {
path: './public',
lookupCompressed: true,
lookupMap: {
gzip: '.gz',
brotli: '.br'
}
}
}
```
--------------------------------
### Directory Handler: Allow Access to Hidden Files
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Enable the `showHidden` option in the directory handler to allow access to files and directories starting with a dot.
```javascript
handler: {
directory: {
path: './public',
showHidden: true // Allow hidden files
}
}
```
--------------------------------
### Disable Hidden Files
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Configure the directory handler to prevent serving or listing files and directories that start with a dot ('.'). This is the default behavior for security.
```javascript
handler: {
directory: {
showHidden: false // Default: blocks .htaccess, .env, etc.
}
}
```
--------------------------------
### Serve Multiple Asset Directories
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
Configure a route to serve assets from multiple directories, with a fallback mechanism to subsequent directories if the file is not found in the primary one.
```javascript
server.route({
method: 'GET',
path: '/assets/{path*}',
handler: {
directory: {
path: [
'./assets', // Primary
'./legacy/assets', // Fallback
'./defaults' // Final fallback
]
}
}
});
```
--------------------------------
### Directory Handler with Multiple Fallback Directories
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This configuration shows how to set up a directory handler with multiple fallback paths to serve files from different locations if the primary path fails.
```javascript
handler: {
directory: {
path: [
'./public', // Primary
'./legacy/public', // Fallback
'./defaults' // Final fallback
]
}
}
```
--------------------------------
### Open File and Get File Descriptor
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Opens a file with a specified mode and returns its file descriptor. This is a wrapper around Node.js's fs.open().
```javascript
const fd = await exports.open('./file.txt', 'r');
```
--------------------------------
### Get File Stats
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Opens a file, retrieves its metadata (stats), logs the size and modification time, and then closes the file. Assumes the file is open.
```javascript
const file = new File('./data.txt');
await file.open('r');
const stats = await file.stat();
console.log(`File size: ${stats.size} bytes`);
console.log(`Modified: ${stats.mtime}`);
file.close();
```
--------------------------------
### Custom Content Encoding Lookup Map Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Define a custom mapping between content encoding names and their corresponding file extensions for the Inert file handler.
```javascript
lookupMap: {
gzip: '.gz',
brotli: '.br',
'custom-encoding': '.custom'
}
```
--------------------------------
### Basic Static Site
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Serves files from a 'public' directory with index.html support.
```APIDOC
## GET /{param*}
### Description
Serves files from a specified directory, with support for index files.
### Method
GET
### Endpoint
`/{param*}`
### Handler Configuration
```json
{
"directory": {
"path": "./public",
"index": true
}
}
```
```
--------------------------------
### Multiple Paths (Fallback) Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Configure the directory handler to attempt serving files from multiple directories in a specified order. The handler tries subsequent directories if a file is not found in the preceding ones, with the first match being served.
```javascript
handler: {
directory: {
path: ['./public', './legacy-public', './fallback']
}
}
```
--------------------------------
### Open and Get File Stats
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Opens a file and immediately retrieves its statistics. If the stat operation fails, the file is closed. Handles errors during opening or stat operations.
```javascript
const file = new File('./config.json');
try {
const stats = await file.openStat('r');
console.log(`File is ${stats.size} bytes`);
// File is now open and ready to read
file.close();
} catch (err) {
// File not opened
}
```
--------------------------------
### Toolkit Method: reply.file()
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
Provides a convenience method within route handlers to serve files with various options.
```APIDOC
## reply.file(path, options)
### Description
Serves a file using the toolkit method, allowing for options like custom filename, content disposition, and ETag generation.
### Method Signature
`reply.file(path: string, options?: ReplyFileHandlerOptions): ResponseObject`
### Parameters
#### Path Parameters
- **path** (string) - Required - Relative or absolute file path.
#### Query Parameters
This method does not directly use query parameters.
#### Request Body
This method does not accept a request body.
#### Options (ReplyFileHandlerOptions)
- **confine** (boolean | string) - Optional - Restrict file serving to a directory. Defaults to `true`.
- **filename** (string) - Optional - Custom filename for Content-Disposition header. Only valid when `mode` is set.
- **mode** ('attachment' | 'inline' | false) - Optional - Include Content-Disposition header. `'attachment'` triggers download, `'inline'` displays in browser. Defaults to `false`.
- **lookupCompressed** (boolean) - Optional - Search for pre-compressed versions (e.g., `.gz` files). Defaults to `false`.
- **lookupMap** ({ [encoding: string]: string }) - Optional - Maps content encodings to file extensions for compressed file lookup. Defaults to `{ gzip: '.gz' }`.
- **etagMethod** ('hash' | 'simple' | false) - Optional - ETag computation method. Defaults to `'hash'`.
- **start** (number) - Optional - Byte offset to start reading from. Defaults to `0`.
- **end** (number) - Optional - Byte offset to stop reading (inclusive). If not set, reads to end of file. Must be >= start.
### Request Example
```javascript
return h.file('resumes/my-resume.pdf', {
mode: 'attachment',
filename: 'resume-2024.pdf',
etagMethod: 'hash'
});
```
### Response
#### Success Response (200)
Response configured to serve the file.
#### Response Example
(Binary content of the file)
```
--------------------------------
### Invalid File Handler: Filename without Mode
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This code example shows an invalid configuration for the file handler where 'filename' is provided without a 'mode', leading to a validation error.
```javascript
handler: {
file: {
path: './',
filename: 'test.pdf' // Error: mode required
}
}
// Throws validation error during route registration
```
--------------------------------
### Example: Path Escapes Confine Directory
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Illustrates a route configuration where the requested file path attempts to escape the specified 'confine' directory, triggering a 403 Forbidden error.
```javascript
// Confine to ./public, but request escapes
server.route({
method: 'GET',
path: '/file',
handler: {
file: {
path: '../../etc/passwd', // Escapes confine
confine: './public'
}
}
});
const res = await server.inject('/file');
// res.statusCode === 403
// res.result.message === 'Forbidden'
```
--------------------------------
### Serve Pre-Compressed Assets
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/file.md
This configuration allows Inert to automatically serve pre-compressed assets (like gzip or brotli) if available, based on the `Accept-Encoding` header. The `lookupCompressed` option should be set to `true`.
```javascript
server.route({
method: 'GET',
path: '/app/{path*}',
config: {
files: { relativeTo: __dirname }
},
handler: {
file: {
path: (request) => `./dist/${request.params.path || 'index.html'}`,
lookupCompressed: true,
etagMethod: 'hash'
}
}
});
```
--------------------------------
### Error Data Example for File Not Found
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
Illustrates the structure of error data when a requested file is not found or a path component is not a directory. This data includes the path that caused the error.
```javascript
{
path: '/path/to/missing/file.txt'
}
```
--------------------------------
### Multiple Fallback Directories
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Configure multiple directories to serve assets, with a fallback mechanism. The handler checks directories in the order they are provided, serving from the first one that contains the requested file.
```javascript
server.route({
method: 'GET',
path: '/assets/{path*}',
handler: {
directory: {
path: [
'./public/assets', // Try primary first
'./legacy/assets', // Fall back to legacy
'./defaults' // Final fallback
],
lookupCompressed: true
}
}
});
```
--------------------------------
### Directory Handler Configuration Validation Examples
Source: https://github.com/hapijs/inert/blob/master/_autodocs/configuration.md
Shows validation errors for the directory handler. 'Path' is mandatory, 'defaultExtension' must be alphanumeric, and the route path requires a trailing parameter.
```javascript
// Path is required
handler: { directory: { /* missing path */ } } // Error
```
```javascript
// defaultExtension must be alphanumeric
handler: { directory: { path: './', defaultExtension: 'my-ext' } } // Error
```
```javascript
// Route must end with parameter
server.route({
path: '/files', // Missing trailing parameter
handler: { directory: { path: './' } } // Error
});
```
--------------------------------
### Serve Public Website
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
Configure a route to serve static files for a public website. Specifies the directory to serve from and the default index file.
```javascript
server.route({
method: 'GET',
path: '/{param*}',
config: {
files: { relativeTo: __dirname }
},
handler: {
directory: {
path: './public',
index: ['index.html'],
redirectToSlash: true,
lookupCompressed: true
}
}
});
```
--------------------------------
### Enable Pre-compression Lookup
Source: https://github.com/hapijs/inert/blob/master/_autodocs/INDEX.md
Configure Inert to look for pre-compressed assets (e.g., .gz files). This reduces transfer size by serving compressed files directly.
```javascript
lookupCompressed: true
```
--------------------------------
### open(path, mode)
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Opens a file at the specified path with the given mode and returns its file descriptor.
```APIDOC
## open(path, mode)
### Description
Open a file and return the file descriptor.
### Parameters
#### Path Parameters
* **path** (`string`) - Required - The path to the file.
* **mode** (`string`) - Required - The mode to open the file in (e.g., 'r', 'w').
#### Query Parameters
* None
#### Request Body
* None
### Request Example
```javascript
const fd = await exports.open('./file.txt', 'r');
```
### Response
#### Success Response (200)
* **File Descriptor** (`number`) - The file descriptor for the opened file.
#### Response Example
```javascript
123
```
```
--------------------------------
### createReadStream (File Instance Method)
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Creates a readable stream for a file associated with a File instance. Allows specifying stream options like start, end, and autoClose behavior.
```APIDOC
## createReadStream(options)
### Description
Create a readable stream for the open file.
### Parameters
#### Path Parameters
* None
#### Query Parameters
* None
#### Request Body
* **options** (`object`) - Optional - Stream options. Defaults to `{}`.
* **options.fd** (`number`) - Optional - File descriptor (automatically set).
* **options.start** (`number`) - Optional - Byte offset to start reading. Defaults to `0`.
* **options.end** (`number`) - Optional - Byte offset to stop reading (inclusive).
* **options.autoClose** (`boolean`) - Optional - Close fd when stream ends. Defaults to `true`.
### Request Example
```javascript
const file = new File('./large-file.bin');
await file.open('r');
const stream = file.createReadStream({
start: 1000,
end: 2000,
autoClose: true
});
stream.on('data', (chunk) => {
console.log(`Read ${chunk.length} bytes`);
});
stream.on('end', () => {
console.log('Done reading');
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
```
### Response
#### Success Response
* **ReadStream** (`object`) - Node.js readable stream
#### Response Example
```javascript
// A Node.js readable stream object
```
```
--------------------------------
### Inert Toolkit Decorations
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
Shows how to use the 'h.file' function within a handler to serve a file, with optional parameters.
```javascript
// Available in handlers via second parameter
server.route({
handler: (request, h) => {
return h.file('./path.txt', options); // Serve file
}
});
```
--------------------------------
### File Handler Byte Range Validation
Source: https://github.com/hapijs/inert/blob/master/_autodocs/REFERENCE_GUIDE.md
The 'start' option must be less than or equal to the 'end' option for byte range requests. This shows invalid and valid byte range configurations.
```javascript
// Wrong:
handler: {
file: {
path: './file.txt',
start: 100,
end: 50 // end < start!
}
}
// Right:
handler: {
file: {
path: './file.txt',
start: 50,
end: 100
}
}
```
--------------------------------
### Plugin Registration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/index.md
Demonstrates how to register the @hapi/inert plugin with a hapi server.
```APIDOC
## Plugin Registration
### Description
Registers the inert plugin with a hapi server. The plugin decorates the server with two handler types (`file` and `directory`) and adds a `file()` method to the response toolkit. It optionally sets up an ETag cache based on server configuration.
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Server Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
| etagsCacheMaxSize | `number` | 10000 | Maximum number of ETags to cache. Set to 0 to disable caching. |
### Usage Example
```javascript
const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const server = new Hapi.Server({
port: 3000,
plugins: {
inert: {
etagsCacheMaxSize: 5000 // Custom ETag cache size
}
}
});
await server.register(Inert);
// Now define routes using file and directory handlers
server.route({
method: 'GET',
path: '/static/{param*}',
handler: {
directory: {
path: './public',
index: true,
listing: true
}
}
});
```
```
--------------------------------
### Handle Failed File Stat Error
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This snippet illustrates handling a 500 error when attempting to get file metadata (stat), which can occur if the file is deleted between open and stat operations.
```javascript
// File deleted between opening and stats:
// res.statusCode === 500
// res.result.message === 'Failed to stat file'
```
--------------------------------
### reply.file() Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/types.md
Configuration options for the `reply.file()` toolkit method, used for serving a single file. It allows customization of download behavior, filename, ETag generation, and byte range requests.
```APIDOC
## ReplyFileHandlerOptions
Configuration options for the `reply.file()` toolkit method.
### Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| confine | `boolean \| string` | No | `true` | Directory confinement. Defaults to route's `files.relativeTo`. |
| filename | `string` | No | Basename of path | Custom filename for download. Only used with `mode` set. |
| mode | `false \| 'attachment' \| 'inline'` | No | `false` | Content-Disposition header. `attachment` triggers download, `inline` displays in browser. |
| lookupCompressed | `boolean` | No | `false` | Look for pre-compressed versions matching Accept-Encoding. |
| lookupMap | `{ [index: string]: string }` | No | `{ gzip: '.gz' }` | Maps content encodings to file extensions. |
| etagMethod | `'hash' \| 'simple' \| false` | No | `'hash'` | ETag generation method. `hash` = SHA1, `simple` = size/mtime, `false` = disabled. |
| start | `number` | No | `0` | Byte offset to start reading from. |
| end | `number` | No | — | Byte offset to stop reading (inclusive). If not set, reads to EOF. |
### Usage Example:
```javascript
const handler = (request, h) => {
return h.file('./reports/report.pdf', {
mode: 'attachment',
filename: `report-${request.params.id}.pdf`,
etagMethod: 'simple'
});
};
server.route({
method: 'GET',
path: '/report/{id}',
config: {
files: { relativeTo: __dirname }
},
handler
});
```
```
--------------------------------
### Error Conversion Map Example
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Illustrates the mapping of file system error codes to hapi Boom errors, including status codes for common errors like 'not found' and 'forbidden'.
```javascript
notFound: new Set(['ENOENT', 'ENOTDIR'])
```
--------------------------------
### Promisified File System Methods
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/fs.md
Lists the file system methods that are promisified using Util.promisify() for easier asynchronous handling.
```javascript
['open', 'close', 'fstat', 'readdir']
```
--------------------------------
### Invalid Directory Handler: Route Missing Trailing Parameter
Source: https://github.com/hapijs/inert/blob/master/_autodocs/errors.md
This example illustrates an invalid configuration for the directory handler where the route path does not end with a parameter (e.g., '{param*}'), causing an assertion error.
```javascript
server.route({
method: 'GET',
path: '/files', // Missing {param*}!
handler: {
directory: { path: './public' }
}
});
// Throws assertion error: "must end with a parameter"
```
--------------------------------
### Distributed Deployment with Hash ETag Consistency
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/etag.md
Demonstrates how hash-based ETags ensure consistency across multiple server instances in a distributed deployment, as they depend on file content.
```javascript
// Server A
const serverA = new Hapi.Server({
plugins: { inert: { etagsCacheMaxSize: 10000 } }
});
// Server B
const serverB = new Hapi.Server({
plugins: { inert: { etagsCacheMaxSize: 10000 } }
});
// Both servers: same file = same ETag
// Because hash is based on file content, not server state
```
--------------------------------
### Download Directory with Listings
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Serves files from a 'downloads' directory and enables directory listings.
```APIDOC
## GET /downloads/{file*}
### Description
Serves files from the 'downloads' directory and allows directory listings to be viewed.
### Method
GET
### Endpoint
`/downloads/{file*}`
### Handler Configuration
```json
{
"directory": {
"path": "./downloads",
"index": false,
"listing": true,
"showHidden": false
}
}
```
```
--------------------------------
### Default Index File Configuration
Source: https://github.com/hapijs/inert/blob/master/_autodocs/api-reference/directory.md
Enable the directory handler to automatically serve an index file when a directory is requested. By default, it looks for 'index.html'.
```javascript
handler: {
directory: {
index: true // Looks for 'index.html'
}
}
```
--------------------------------
### File Handler Configuration Options
Source: https://github.com/hapijs/inert/blob/master/_autodocs/INDEX.md
Details the route-level configuration options specific to the file handler.
```javascript
Route-Level File Handler
- path (string | function, **required**)
- confine (boolean | string, default true)
- filename (string)
- mode ('attachment' | 'inline' | false)
- lookupCompressed (boolean)
- lookupMap (object)
- etagMethod ('hash' | 'simple' | false)
- start (number, default 0)
- end (number)
```