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 Serving
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.html

Static 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 /cdn
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 Configuration
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

Related Features