Content Handling
Static Files
Serve static files with caching, ETags, and optimized headers for efficient asset delivery.
Basic Static File Serving
MoroJS provides built-in static file serving middleware with support for caching, ETags, and optimized headers.
Basic Static File Servingtypescript
1import { createApp, middleware } from '@morojs/moro';
2
3const app = await createApp();
4
5app.use(middleware.staticFiles({
6 root: './public',
7 maxAge: 3600, // seconds — sets Cache-Control: public, max-age=3600
8 index: ['index.html', 'index.htm']
9}));
10
11// Files in ./public are now accessible
12// GET /styles.css -> ./public/styles.css
13// GET / -> ./public/index.htmlStatic File Features
- Automatic file serving from directory
- Mount at the URL root or under a prefix
- ETag support with conditional 304 responses
- Cache-Control via maxAge (seconds)
- Directory index support
- Dotfile policy: allow, deny, or ignore
- Directory-traversal and symlink-escape protection
Mounting Under a Prefix
Pass a prefix to serve a directory under a URL mount point. The prefix is stripped before the path is resolved against root, and requests outside it fall straight through to the rest of your app.
Serving Assets Under /cdntypescript
1import { createApp, middleware } from '@morojs/moro';
2
3const app = await createApp();
4
5app.use(middleware.staticFiles({
6 root: './public',
7 prefix: '/cdn',
8 maxAge: 86400
9}));
10
11// ./public/app.css -> GET /cdn/app.css
12// ./public/index.html -> GET /cdn
13// GET /app.css -> not static; continues to your routes
14
15// Mount several roots by registering the middleware more than once
16app.use(middleware.staticFiles({ root: './uploads', prefix: '/files' }));How the prefix is matched
- Matching is exact and case-sensitive: /cdn never matches a sibling path like /cdn-backup
- The prefix goes in the options object — app.use('/cdn', staticFiles(...)) is silently ignored, because a path passed as argument 1 is only honoured when argument 2 is a createRouter() instance
- Omit prefix to serve from the URL root, where ./public/assets/app.css serves as GET /assets/app.css
Advanced Configuration
Advanced Static File Configurationtypescript
1import { createApp, middleware } from '@morojs/moro';
2
3const app = await createApp();
4
5// StaticOptions
6app.use(middleware.staticFiles({
7 root: './public', // required; resolved to an absolute path
8 prefix: '/assets', // URL mount point; defaults to the URL root
9 maxAge: 86400, // seconds; 0 omits Cache-Control
10 etag: true, // default true; enables conditional 304s
11 lastModified: true, // default true; answers If-Modified-Since
12 acceptRanges: true, // default true; serves Range requests as 206
13 index: ['index.html'], // default ['index.html', 'index.htm']
14 dotfiles: 'ignore' // 'allow' | 'deny' | 'ignore'
15}));
16
17// When no file matches, the middleware calls next() and your routes run.Conditional and partial requests
- Responses carry ETag and Last-Modified; a conditional request on either returns 304, with If-None-Match taking precedence
- A Range request is answered with 206 and Content-Range — what media seeking and resumable downloads need
- An unsatisfiable range returns 416; a request for several ranges at once returns the whole entity rather than a multipart body
- If-Range falls back to the full entity when the file changed since the client last saw it
Large files
- Files above 512 KB are streamed, so serving one does not cost its full size in memory per request
- A Range reads only the requested slice
- Smaller files keep the single-read path
Best Practices
Do
- Use long cache times for static assets
- Enable ETags for cache validation
- Use CDN for production deployments
- Set appropriate cache headers
- Use versioned filenames for cache busting
- Organize files by type
Don't
- Serve sensitive files as static
- Use short cache times for assets
- Disable ETags unnecessarily
- Serve large files directly
- Allow directory listing
- Skip cache headers