npm.io
1.0.0 • Published 8 months ago

@flink-app/static-files-plugin

Licence
MIT
Version
1.0.0
Deps
2
Size
18 kB
Vulns
0
Weekly
0

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:

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

Configuration

Basic Setup

Configure the plugin to serve static files from a directory:

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:

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:

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:

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:

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:

npm install --save-dev copyfiles

Add scripts to your package.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:

npm install --save-dev npm-run-all copyfiles
{
  "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):

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:

{
  "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:

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:

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:

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
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:

// 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:

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:

// 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:

    console.log(join(__dirname, "public"));
    // Verify this path exists
  2. Verify files were copied:

    ls dist/src/public/
    # Should show your static files
  3. Check URL path:

    // 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

    // 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/

    npm run copy-files
MIME Type Issues

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

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
interface StaticOptions {
  path: string;    // Base URL path (must start with "/")
  folder: string;  // Absolute filesystem path to static files directory
}
Plugin Function
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)