A minimal static file handler for @chubbyts/chubbyts-http-types.
It works with any implementation of the types (e.g. @chubbyts/chubbyts-http): the request headers get read and the responses get created and changed through the injected response factory and helpers only.
- node: 22
- @chubbyts/chubbyts-dic-config-factory: ^1.0.1
- @chubbyts/chubbyts-dic-types: ^2.3.1
- @chubbyts/chubbyts-http-error: ^3.5.1
- @chubbyts/chubbyts-http-types: ^4.0.0
Through NPM as @chubbyts/chubbyts-http-static-file.
npm i @chubbyts/chubbyts-http-static-file@^4.0.0import { createResponseFactory, createServerRequestFactory } from '@chubbyts/chubbyts-http/message-factory';
import { createResponseHelpers } from '@chubbyts/chubbyts-http/message-helper';
import { createStaticFileHandler } from '@chubbyts/chubbyts-http-static-file/handler';
import mimeTypes from '@chubbyts/chubbyts-http-static-file/mimetypes';
const serverRequestFactory = createServerRequestFactory();
const responseFactory = createResponseFactory();
const responseHelpers = createResponseHelpers();
const handler = createStaticFileHandler(
responseFactory,
responseHelpers,
'/path/to/public/directory',
mimeTypes,
// 'md5',
);
(async () => {
const serverRequest = serverRequestFactory('GET', 'https://example.com/images/logo.png');
const response = await handler(serverRequest);
})();Only GET and HEAD requests are allowed (otherwise a 405 http error with an allow header gets thrown), the file
gets resolved by the (percent decoded) path of the uri within the public directory (otherwise, also for symlinks
pointing outside of it, a 404 http error gets thrown). Errors not meaning a missing or inaccessible file (e.g. EMFILE
or EIO) get rethrown as they are, as well as the abort reason of the request signal. The response contains the
content-length, content-type, etag (the hash of the file) and x-content-type-options: nosniff headers, a
request with a matching if-none-match header gets a 304 without body. The body gets destroyed (and the file closed)
when the request signal gets aborted.
The handler is minimal by intention: there is no support for range, last-modified / if-modified-since or
cache-control, and the file gets hashed on each request (the cost grows with the file size).
- All files within the public directory get served, dotfiles (e.g.
.envor.git) included. - The
content-typeis derived from the extension, so uploaded.htmlor.svgfiles run scripts within the origin of the application: serve user uploads from a separate origin or setcontent-disposition: attachmentand a restrictivecontent-security-policythrough a middleware. - The containment is checked on the resolved path, a symlink swapped in as the last component gets rejected when opening the file. Untrusted parties must not be able to create symlinks or directories within the public directory.
The package ships service factories (abstract factories built on chubbyts-dic-config-factory) for a chubbyts-dic-config (or any chubbyts-dic-types compatible) container within @chubbyts/chubbyts-http-static-file/service-factory, configured through config.chubbyts.staticFile. The implementation of the types gets read from the services responseFactory and responseHelpers, which have to be registered:
import type { ConfigFactory } from '@chubbyts/chubbyts-dic-config/dist/dic-config';
import { createContainerByConfigFactory } from '@chubbyts/chubbyts-dic-config/dist/dic-config';
import { createResponseFactory } from '@chubbyts/chubbyts-http/message-factory';
import { createResponseHelpers } from '@chubbyts/chubbyts-http/message-helper';
import type { StaticFileConfig } from '@chubbyts/chubbyts-http-static-file/service-factory';
import { staticFileHandlerServiceFactory } from '@chubbyts/chubbyts-http-static-file/service-factory';
import type { Handler } from '@chubbyts/chubbyts-http-types/handler';
const container = createContainerByConfigFactory({
chubbyts: {
staticFile: {
publicDirectory: '/path/to/public/directory', // required
// hashAlgorithm: 'md5',
} satisfies StaticFileConfig,
},
dependencies: {
factories: new Map<string, ConfigFactory>([
['responseFactory', () => createResponseFactory()],
['responseHelpers', () => createResponseHelpers()],
['staticFileHandler', staticFileHandlerServiceFactory()],
]),
},
})();
const staticFileHandler = container.get<Handler>('staticFileHandler');The staticFileHandlerServiceFactory uses the service staticFileMimeTypes of the container if registered, and creates it through the shipped mimeTypesServiceFactory (the bundled mime types) otherwise. Register it under its name to replace the mime types (e.g. a reduced or extended Map) or to share them with other services.
To serve different directories, the same factories can be registered multiple times with a name: the config is then read from config.chubbyts.staticFile.<name> and the name gets appended to each service id (staticFileHandlerassets, staticFileMimeTypesassets, ...).
const container = createContainerByConfigFactory({
chubbyts: {
staticFile: {
assets: { publicDirectory: '/path/to/assets' },
uploads: { publicDirectory: '/path/to/uploads', hashAlgorithm: 'sha1' },
} satisfies Record<string, StaticFileConfig>,
},
dependencies: {
factories: new Map<string, ConfigFactory>([
// responseFactory and responseHelpers like above
['staticFileHandlerassets', staticFileHandlerServiceFactory('assets')],
['staticFileHandleruploads', staticFileHandlerServiceFactory('uploads')],
]),
},
})();
const assetsHandler = container.get<Handler>('staticFileHandlerassets');
const uploadsHandler = container.get<Handler>('staticFileHandleruploads');2026 Dominik Zogg