# @flink-app/static-files-plugin

> Flink plugin that make flink serve static files

Latest version **1.0.0** (published 2026-02-01) · MIT license · 0 weekly downloads

## Install

```sh
npm install @flink-app/static-files-plugin
pnpm add @flink-app/static-files-plugin
yarn add @flink-app/static-files-plugin
bun add @flink-app/static-files-plugin
```

## Health

**Score 55/100 (C)** — status: stable.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2026-02-01 |
| First published | 2021-06-22 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 2 |
| Unpacked size | 17.8 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | johan@frost.se |
| Maintainers | joelso, jenkins-frost, johanfrost |

## Links

- npm: https://www.npmjs.com/package/@flink-app/static-files-plugin
- npm.io page: https://npm.io/package/@flink-app/static-files-plugin

## Dependencies (2)

- [node-color-log](https://npm.io/package/node-color-log.md) ^5.3.1
- [@flink-app/flink](https://npm.io/package/@flink-app/flink.md) 1.0.0

## Recent versions

- 1.0.0 (latest) — 2026-02-01
- 2.0.0-alpha.123 (alpha) — 2026-09-30
- 2.0.0-alpha.122 — 2026-09-28
- 2.0.0-alpha.121 — 2026-09-19
- 2.0.0-alpha.120 — 2026-09-18
- 2.0.0-alpha.119 — 2026-09-13
- 2.0.0-alpha.118 — 2026-09-11
- 2.0.0-alpha.117 — 2026-09-06
- 2.0.0-alpha.116 — 2026-09-06
- 2.0.0-alpha.115 — 2026-09-04
- 2.0.0-alpha.114 — 2026-09-01
- 2.0.0-alpha.113 — 2026-08-30
- 2.0.0-alpha.112 — 2026-08-26
- 2.0.0-alpha.111 — 2026-08-22
- 2.0.0-alpha.110 — 2026-08-19
- … 144 more at https://npm.io/package/@flink-app/static-files-plugin/versions

## README

# Static Files Plugin

A Flink plugin for serving static files (HTML, CSS, JavaScript, images, etc.) through your Flink application using Express's built-in static file serving middleware.

## Installation

Install the plugin to your Flink app project:

```bash
npm install @flink-app/static-files-plugin
```

## Configuration

### Basic Setup

Configure the plugin to serve static files from a directory:

```typescript
import { FlinkApp } from "@flink-app/flink";
import { staticFilesPlugin } from "@flink-app/static-files-plugin";
import { join } from "path";

function start() {
  new FlinkApp<AppContext>({
    name: "My app",
    plugins: [
      staticFilesPlugin({
        path: "/",                          // URL path to serve files from
        folder: join(__dirname, "public")   // Filesystem path to static files
      })
    ],
  }).start();
}
```

**Plugin Options:**

```typescript
interface StaticOptions {
  path: string;    // Base URL path for static files (e.g., "/", "/assets", "/static")
  folder: string;  // Absolute path to the folder containing static files
}
```

## Usage Examples

### Serve Files from Root Path

Serve static files directly from the root URL:

```typescript
import { join } from "path";

staticFilesPlugin({
  path: "/",
  folder: join(__dirname, "public")
})

// Files accessible at:
// http://localhost:3000/index.html
// http://localhost:3000/styles.css
// http://localhost:3000/logo.png
```

### Serve Files from Subdirectory

Serve static files from a specific URL path:

```typescript
import { join } from "path";

staticFilesPlugin({
  path: "/assets",
  folder: join(__dirname, "public")
})

// Files accessible at:
// http://localhost:3000/assets/index.html
// http://localhost:3000/assets/styles.css
// http://localhost:3000/assets/logo.png
```

### Multiple Static Directories

Serve different directories at different paths:

```typescript
import { join } from "path";

new FlinkApp<AppContext>({
  name: "My app",
  plugins: [
    // Serve images
    staticFilesPlugin({
      path: "/images",
      folder: join(__dirname, "assets/images")
    }),

    // Serve CSS/JS
    staticFilesPlugin({
      path: "/static",
      folder: join(__dirname, "assets/static")
    }),

    // Serve HTML pages
    staticFilesPlugin({
      path: "/",
      folder: join(__dirname, "public")
    })
  ]
}).start();

// Files accessible at:
// http://localhost:3000/images/logo.png
// http://localhost:3000/static/app.js
// http://localhost:3000/index.html
```

## File Types Supported

The plugin can serve any static file type, including:

- **HTML**: `.html`, `.htm`
- **CSS**: `.css`
- **JavaScript**: `.js`, `.mjs`
- **Images**: `.jpg`, `.jpeg`, `.png`, `.gif`, `.svg`, `.webp`, `.ico`
- **Fonts**: `.woff`, `.woff2`, `.ttf`, `.eot`
- **Documents**: `.pdf`, `.txt`, `.json`, `.xml`
- **Media**: `.mp4`, `.webm`, `.mp3`, `.ogg`, `.wav`
- And more...

## Directory Structure Example

Typical project structure with static files:

```
my-flink-app/
├── src/
│   ├── index.ts          # App startup
│   ├── handlers/         # API handlers
│   └── public/           # Static files (source)
│       ├── index.html
│       ├── styles.css
│       ├── app.js
│       └── images/
│           └── logo.png
└── dist/                 # Built output
    └── src/
        └── public/       # Static files (copied)
```

## Copying Static Files to Dist

Flink's TypeScript compiler only copies `.ts` and `.json` files by default. Static files need to be copied manually to the `dist` folder.

### Method 1: Using copyfiles Package

Install the `copyfiles` package:

```bash
npm install --save-dev copyfiles
```

Add scripts to your `package.json`:

```json
{
  "scripts": {
    "copy-files": "copyfiles -u 1 src/public/**/* dist/src/",
    "predev": "npm run copy-files",
    "prebuild": "npm run copy-files",
    "dev": "nodemon",
    "build": "tsc -p tsconfig.dist.json"
  }
}
```

**Explanation:**
- `copyfiles -u 1 src/public/**/* dist/src/` copies all files from `src/public/` to `dist/src/public/`
- `-u 1` removes the first directory level (strips `src/`)
- `predev` and `prebuild` run automatically before `dev` and `build` scripts

### Method 2: Using npm-run-all

For parallel copying of multiple directories:

```bash
npm install --save-dev npm-run-all copyfiles
```

```json
{
  "scripts": {
    "copy:public": "copyfiles -u 1 src/public/**/* dist/src/",
    "copy:assets": "copyfiles -u 1 src/assets/**/* dist/src/",
    "copy:all": "npm-run-all copy:*",
    "predev": "npm run copy:all",
    "prebuild": "npm run copy:all"
  }
}
```

### Method 3: Using a Custom Script

Create a copy script (`scripts/copy-static.js`):

```javascript
const fs = require("fs-extra");
const path = require("path");

const source = path.join(__dirname, "../src/public");
const dest = path.join(__dirname, "../dist/src/public");

fs.copySync(source, dest, {
  overwrite: true,
  errorOnExist: false
});

console.log("Static files copied successfully!");
```

Add to `package.json`:

```json
{
  "scripts": {
    "copy-files": "node scripts/copy-static.js",
    "predev": "npm run copy-files",
    "prebuild": "npm run copy-files"
  }
}
```

## Complete Example

Here's a complete example of serving a frontend application:

```typescript
import { FlinkApp } from "@flink-app/flink";
import { staticFilesPlugin } from "@flink-app/static-files-plugin";
import { join } from "path";
import { Ctx } from "./Ctx";

function start() {
  new FlinkApp<Ctx>({
    name: "My Full-Stack App",
    db: {
      uri: process.env.MONGODB_URI!
    },
    plugins: [
      // Serve frontend application
      staticFilesPlugin({
        path: "/",
        folder: join(__dirname, "public")
      }),

      // Serve uploaded files
      staticFilesPlugin({
        path: "/uploads",
        folder: join(__dirname, "../uploads")
      })
    ]
  }).start();
}

start();
```

**Project Structure:**
```
dist/
└── src/
    ├── index.js           # Compiled app
    ├── public/            # Frontend files
    │   ├── index.html
    │   ├── app.js
    │   └── styles.css
    └── uploads/           # User uploads
        └── image.jpg
```

**Accessible URLs:**
- `http://localhost:3000/` → `dist/src/public/index.html`
- `http://localhost:3000/app.js` → `dist/src/public/app.js`
- `http://localhost:3000/styles.css` → `dist/src/public/styles.css`
- `http://localhost:3000/uploads/image.jpg` → `dist/src/uploads/image.jpg`

## Advanced Usage

### SPA (Single Page Application) Support

For React, Vue, Angular apps that use client-side routing:

```typescript
import { FlinkApp } from "@flink-app/flink";
import { staticFilesPlugin } from "@flink-app/static-files-plugin";
import express from "express";
import { join } from "path";

const app = new FlinkApp<Ctx>({
  name: "SPA App",
  plugins: [
    // Serve static assets
    staticFilesPlugin({
      path: "/",
      folder: join(__dirname, "public")
    })
  ]
});

// Fallback to index.html for client-side routing
app.expressApp?.get("*", (req, res) => {
  res.sendFile(join(__dirname, "public/index.html"));
});

app.start();
```

### Serving with Cache Headers

Modify Express static options for better caching:

```typescript
import { FlinkApp } from "@flink-app/flink";
import express from "express";
import { join } from "path";

const app = new FlinkApp<Ctx>({
  name: "My app",
  plugins: []
});

// Manually configure express.static with options
const staticPath = join(__dirname, "public");
app.expressApp?.use("/", express.static(staticPath, {
  maxAge: "1d",           // Cache for 1 day
  etag: true,             // Enable ETags
  lastModified: true,     // Enable Last-Modified headers
  index: ["index.html"]   // Default index files
}));

app.start();
```

### Serving Different Files in Development vs Production

```typescript
import { FlinkApp } from "@flink-app/flink";
import { staticFilesPlugin } from "@flink-app/static-files-plugin";
import { join } from "path";

const isDev = process.env.NODE_ENV !== "production";

new FlinkApp<Ctx>({
  name: "My app",
  plugins: [
    staticFilesPlugin({
      path: "/",
      folder: isDev
        ? join(__dirname, "../public")      // Development: src/public
        : join(__dirname, "public")          // Production: dist/src/public
    })
  ]
}).start();
```

## Best Practices

### 1. Use Absolute Paths

Always use `join(__dirname, ...)` for absolute paths:

```typescript
// Good
folder: join(__dirname, "public")

// Bad - relative paths can cause issues
folder: "./public"
```

### 2. Order of Plugins Matters

Register API handlers before static file plugins to avoid conflicts:

```typescript
new FlinkApp<Ctx>({
  name: "My app",
  plugins: [
    apiDocsPlugin({ path: "/docs" }),      // API endpoints first
    staticFilesPlugin({ path: "/" })       // Static files last
  ]
})
```

If static files are registered first, they might intercept API routes.

### 3. Use Subdirectories for Assets

Avoid serving from root in production:

```typescript
// Development - convenient
staticFilesPlugin({
  path: "/",
  folder: join(__dirname, "public")
})

// Production - better organization
staticFilesPlugin({
  path: "/static",
  folder: join(__dirname, "public")
})
```

### 4. Security Considerations

- **Never serve sensitive files**: Don't put `.env`, config files, or database files in the static directory
- **Use proper permissions**: Ensure the static files directory has appropriate read permissions
- **Validate file paths**: The plugin uses Express's built-in static middleware which has path traversal protection

### 5. Performance Optimization

For production, consider:
- Using a CDN for static assets
- Setting up Nginx/Apache as a reverse proxy to serve static files
- Enabling gzip/brotli compression at the web server level
- Using `express.static` options for cache headers

## Common Issues

### Files Not Loading

1. **Check file path:**
   ```typescript
   console.log(join(__dirname, "public"));
   // Verify this path exists
   ```

2. **Verify files were copied:**
   ```bash
   ls dist/src/public/
   # Should show your static files
   ```

3. **Check URL path:**
   ```typescript
   // If path is "/assets"
   staticFilesPlugin({ path: "/assets", folder: "..." })
   // Access at: http://localhost:3000/assets/file.html
   ```

### 404 Errors

1. **Case sensitivity**: File paths are case-sensitive on Linux/Mac
   - `index.HTML` ≠ `index.html`

2. **Path matching**: Ensure URL path matches configured path
   ```typescript
   // With path: "/static"
   // ✓ http://localhost:3000/static/app.js
   // ✗ http://localhost:3000/app.js
   ```

### Files Not Updating

1. **Clear browser cache**: Hard refresh (Ctrl+Shift+R / Cmd+Shift+R)

2. **Restart the server**: Changes to static files require restart unless using hot reload

3. **Re-run copy script**: If files are in `src/`, make sure they're copied to `dist/`
   ```bash
   npm run copy-files
   ```

### MIME Type Issues

Express automatically sets correct MIME types. If you encounter issues:

```typescript
import express from "express";
import { join } from "path";

app.expressApp?.use("/", express.static(join(__dirname, "public"), {
  setHeaders: (res, path) => {
    if (path.endsWith(".js")) {
      res.setHeader("Content-Type", "application/javascript");
    }
  }
}));
```

## API Reference

### Plugin Options

```typescript
interface StaticOptions {
  path: string;    // Base URL path (must start with "/")
  folder: string;  // Absolute filesystem path to static files directory
}
```

### Plugin Function

```typescript
function staticFilesPlugin(options: StaticOptions): FlinkPlugin
```

**Returns:** A Flink plugin that registers Express static middleware.

**Logs:** When initialized, logs: `"Registered static file route {path}"`

## Notes

- The plugin uses Express's `express.static` middleware internally
- Files are served with appropriate MIME types automatically
- Directory listings are not enabled by default (returns 404 for directories)
- The plugin does not add any context to `ctx.plugins`
- Multiple instances can be registered for different paths
- The `folder` path should be absolute (use `join(__dirname, ...)`)
- Static files are served with default cache headers (can be customized via Express static options)

---
_Source: https://npm.io/package/@flink-app/static-files-plugin · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
