express-autoindex
produce a directory listing like Nginx, Apache or another, but just with express
It takes into consideration most of the mime-types correctly, and page generation is fully customizable.
The objectives are:
- Make a
HTML
page orJSON
data easily usable, on the great majority of browsers - Correctly take into consideration the majority of
MIME types
, and the generation of the most customizable pages possible - Supports most common file encodings
- Native support for
Typescript
,EcmaScript
andCommonJS
- The least amount of dependency possible (currently only
two
) - The
lightest
possible
- Install
- API
- Customization of the html page appearance
- Customization of the date format
- Customization of the JSON format
- Error handling
- Minimalist example
- Production mode
- Contributors
- To do list
- License
- Dependencies
# npm
npm install express-autoindex
# yarn
yarn add express-autoindex
import autoindex from 'express-autoindex';
// Root of server, ./public dir
app.use(autoindex('public'))
// Specific path `/files`, ./public dir
app.use('/files', autoindex('public'));
// Set options
app.use('/files', autoindex('public', { dirAtTop: false, displaySize: false }));
Returns middlware that serves an index of the directory in the given path
.
The path
is based of the req.url
value, so a req.url
of '/some/dir
with a path
of 'public'
will look at 'public/some/dir'
.
express-autoindex
accepts options:
-
type:
boolean
Throw error for all HTTP error codes (4xx & 5xx).
By default, errors will be generated only on 5xx types. If you wish to generate an error regardless of the HTTP error code, pass
true
to the option.Default to
false
-
type:
number | false
Caches for a defined time the generated pages. Very useful to save server resources.
Pass
false
to disable the cache, or the number of milliseconds representing the cache expiration time.Default to
300000
= 5 mins -
type:
object
{ isDir: string, name: string, path: string, time: string, size: string }
By default, the json generated for a file or folder follows a precise structure. It is possible to rename or remove the key of this object.
More detail here
Default to
undefined
-
type:
string
Pass the relative path of your custom template file. For example, if the file is located in the same folder of your startup server file, simply write
my-file.html
or./my-file.html
.More detail here
Default to
undefined
-
type:
string
Custom date print format.
More detail here
Default to
undefined
-
type:
boolean
Display directories before files
Default to
true
-
type:
boolean
Display the last modification date of the file or directory if available.
Default to
true
-
type:
boolean
Display dotfiles (.env, .yarnrc, ...).
Default to
false
-
type:
boolean
Display size of the file or directory if available.
Default to
true
-
type:
RegExp
Regular expression for files/dirs exclude, for example
/my-file.json|\*.cpp/
. -
type:
boolean
Send data in json format instead of an html page. Might be useful if you want to use the data for another application.
Default to
false
-
type:
boolean
Allow only
HEAD
andGET
HTTP methods.Default to
true
It is possible to customize the entire HTML page sent to the client. To do this, write an HTML page as usual. Then simply pass the path to your file to the customTemplate
option.
express-autoindex
generates two variables:
- title : the title of the generated page. Since this is an autoindex, the title represents the folder path
- content : the contents of an html table representing the contents of the folder
To use variables in your template, simply call them between two curly brackets, like {{title}}
.
Elements inside a row of table all have their own css class linked to it, for easy access via a css selector. These classes are all placed on a :
- link : url pointing to a file or folder
- size : folder or file size
- time : date of last folder or file modification
Don't forget that by default, every browser provides its own native css. If you want to standardize the look and feel, a little css will be necessary.
The html code below is the one generated by default. You can use it as inspiration to generate your own template.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{title}}</title>
</head>
<body>
<h1>{{title}}</h1>
<hr/>
<table>{{content}}</table>
<hr/>
</body>
<style type="text/css">
html {
font-family: Arial, Helvetica, sans-serif
}
table {
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
font-weight: 400;
letter-spacing: normal;
line-height: normal;
font-style: normal;
}
tr td:first-child {
min-width: 20%;
}
td a {
margin-right: 1em;
}
td.size {
text-align: end;
}
</style>
</html>
It is possible to customize the entire date format of file or folder. To do this, pass a string containing the new format to the dateFormat
option.
The date is a string
containing the desired keys, by default %d?-%mo-%y %h:%mi
.
It is formatted in UTC format, and has eight keys:
- %wd → week day
- %d → day
- %mo → month
- %y → year
- %h → hours
- %mi → minutes
- %s → seconds
- %ms → milliseconds
It's possible for one of these keys to return an empty string
if nothing is available.
Now let's imagine that a separator is just after it. If it appears when the key is empty, it's not aesthetically pleasing.
You can therefore add the ? character just after a key. This super character will ensure that the one following it only appears if the key is not empty.
For example, with the following format %d?-%y
, if %d is empty, the string returned will be 2023
and not -2023
.
US: %mo-%d?-$y %h:%mi
EU: %d?-%mo-%y %h:%mi
ISO: %y-%mo-%dT%h:%mi:%sZ
Custom format: <%d?-%y ~ %h>
It is possible to customize the entire JSON format of file or folder. To do this, pass an object containing the new key names to the customJsonFormat
option.
By default, the object generated for an element is:
- isDir → (boolean) is a folder
- name → (string) the name
- path → (string) the path (url)
- time → (string) the UTC date of the last modification
- size → (number) file size,
null
on a folder
The option object works according to three simple rules:
- To change the name of the key, simply write an entry with the key you wish to change and the value representing its new name.
- If a key is missing, it will not be added.
- Any key that does not exist by default will not be added.
Here are a few examples to help you understand how it works:
Options | Generated JSON |
{
"isDir": "isADirectory",
"name": "nameOfElement",
"path": "url",
"time": "date",
"size": "weight"
} |
{
"isADirectory": false,
"nameOfElement": "history.md",
"url": "/public/random_dir/history.md",
"date": "07-Jul-2022 08:19",
"weight": 348
} |
{
"name": "nameOfElement",
"path": "path"
} |
{
"nameOfElement": "history.md",
"path": "/public/random_dir/history.md"
} |
{
"name": "name",
"nonexistentKey": "defaultValue"
} |
{
"name": "history.md"
} |
express-autoindex
will do its best to handle Node.js errors correctly by converting them into a valid HTTP error. The default error type is 500.
In no case express-autoindex
handles custom error pages. The only thing done is to modify the statusCode of the res
object and generate an error if necessary.
Below is a list of currently supported errors.
This is how to read the list: The Node error code → (the related HTTP code) "The error message".
- EACCES → (500) Permission denied
- EADDRINUSE → (500) Address already in use
- EBADF → (500) fd is not a valid open file descriptor
- ECONNREFUSED → (500) Connection refused
- ECONNRESET → (500) Connection reset by peer
- EEXIST → (500) File exists
- EFAULT → (500) Bad address
- EINVAL → (500) Invalid flag specified in flag
- EISDIR → (500) Is a directory
- ELOOP → (500) Too many symbolic links encountered while traversing the path
- EMFILE → (500) Too many open files in system
- ENAMETOOLONG → (414) URI Too Long
- ENOENT → (404) No such file or directory
- ENOMEM → (500) Out of memory
- ENOTDIR → (404) Not a directory
- ENOTEMPTY → (500) Directory not empty
- ENOTFOUND → (500) DNS lookup failed
- EOVERFLOW → (500) pathname or fd refers to a file whose size, inode number, or number of blocks cannot be represented in, respectively, the types off_t, ino_t, or blkcnt_t
- EPERM → (403) Operation not permitted
- EPIPE → (500) Broken pipe
- ETIMEDOUT → (408) Request Timeout
To handle these errors, all you need to do after calling this middleware is to use a code of this type:
import { STATUS_CODES } from 'http';
[...]
app.use((_req, res) => {
// 4xx errors
res.send(`<h1>${res.statusCode} ${STATUS_CODES[res.statusCode]}</h1>`);
});
app.use((err, _req, res, next) => {
// 5xx errors
res.send(`<h1>${res.statusCode} ${STATUS_CODES[res.statusCode]}</h1>`);
if (err)
console.error(err);
next();
});
[...]
import express from 'express';
import autoindex from 'express-autoindex';
import type { Application, NextFunction, Request, Response } from 'express';
const app: Application = express();
const PORT = process.env.PORT || 3000;
app.disable('x-powered-by');
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use('/public', autoindex('public'));
app.listen(PORT, (): void => console.log(`server is running at ${PORT}`));
When the variable process.env.NODE_ENV
is set to production, error messages are much less detailed for security reasons.
Thanks to you and your help, express-autoindex is getting better every day. I would like to thank those people who gave their time 🧡
- Open the middleware for use outside express.js