@flink-app/static-files-plugin
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 fromsrc/public/todist/src/public/-u 1removes the first directory level (stripssrc/)predevandprebuildrun automatically beforedevandbuildscripts
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.htmlhttp://localhost:3000/app.js→dist/src/public/app.jshttp://localhost:3000/styles.css→dist/src/public/styles.csshttp://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.staticoptions for cache headers
Common Issues
Files Not Loading
Check file path:
console.log(join(__dirname, "public")); // Verify this path existsVerify files were copied:
ls dist/src/public/ # Should show your static filesCheck URL path:
// If path is "/assets" staticFilesPlugin({ path: "/assets", folder: "..." }) // Access at: http://localhost:3000/assets/file.html
404 Errors
Case sensitivity: File paths are case-sensitive on Linux/Mac
index.HTML≠index.html
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
Clear browser cache: Hard refresh (Ctrl+Shift+R / Cmd+Shift+R)
Restart the server: Changes to static files require restart unless using hot reload
Re-run copy script: If files are in
src/, make sure they're copied todist/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.staticmiddleware 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
folderpath should be absolute (usejoin(__dirname, ...)) - Static files are served with default cache headers (can be customized via Express static options)