Flink plugin that make flink serve static files
npm install @flink-app/static-files-pluginA Flink plugin for serving static files (HTML, CSS, JavaScript, images, etc.) through your Flink application using Express's built-in static file serving middleware.
Install the plugin to your Flink app project:
``bash`
npm install @flink-app/static-files-plugin
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
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
}
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 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
`
Serve different directories at different paths:
`typescript
import { join } from "path";
new FlinkApp
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
`
The plugin can serve any static file type, including:
- HTML: .html, .htm.css
- CSS: .js
- JavaScript: , .mjs.jpg
- Images: , .jpeg, .png, .gif, .svg, .webp, .ico.woff
- Fonts: , .woff2, .ttf, .eot
- Documents: , .txt, .json, .xml.mp4
- Media: , .webm, .mp3, .ogg, .wav
- And more...
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)
Flink's TypeScript compiler only copies .ts and .json files by default. Static files need to be copied manually to the dist folder.
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
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"
}
}
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"
}
}
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
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
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
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();
`
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
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();
`
`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
name: "My app",
plugins: [
staticFilesPlugin({
path: "/",
folder: isDev
? join(__dirname, "../public") // Development: src/public
: join(__dirname, "public") // Production: dist/src/public
})
]
}).start();
`
Always use join(__dirname, ...) for absolute paths:
`typescript
// Good
folder: join(__dirname, "public")
// Bad - relative paths can cause issues
folder: "./public"
`
Register API handlers before static file plugins to avoid conflicts:
`typescript`
new FlinkApp
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.
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")
})
`
- 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
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
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
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
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
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");
}
}
}));
`
`typescript`
interface StaticOptions {
path: string; // Base URL path (must start with "/")
folder: string; // Absolute filesystem path to static files directory
}
`typescript`
function staticFilesPlugin(options: StaticOptions): FlinkPlugin
Returns: A Flink plugin that registers Express static middleware.
Logs: When initialized, logs: "Registered static file route {path}"
- The plugin uses Express's express.static middleware internallyctx.plugins
- 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 folder
- Multiple instances can be registered for different paths
- The path should be absolute (use join(__dirname, ...)`)
- Static files are served with default cache headers (can be customized via Express static options)