serveFile() - Val Town Utility

serveFile() is a Val Town utility function that serves files from your project with proper content types and handling.

Import

import { serveFile } from "https://esm.town/v/std/utils/index.ts";

What It Does

  • Reads a file from your Val Town project
  • Returns it as an HTTP response
  • Automatically sets the correct Content-Type header based on file extension
  • Handles file not found errors gracefully

Basic Usage

import { serveFile } from "https://esm.town/v/std/utils/index.ts"; import { Hono } from "https://esm.sh/hono"; const app = new Hono(); // Serve a specific file app.get("/", (c) => serveFile("/frontend/index.html", import.meta.url)); // Serve any file in the frontend directory app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url));

Parameters

  1. path (string) - The file path within your project (e.g., "/frontend/index.html")
  2. import.meta.url - Always pass this as the second parameter (tells the function where your project is located)

Content Type Detection

serveFile() automatically sets the correct Content-Type header:

File ExtensionContent-Type
.htmltext/html
.csstext/css
.js, .mjsapplication/javascript
.jsonapplication/json
.pngimage/png
.jpg, .jpegimage/jpeg
.svgimage/svg+xml
.txttext/plain
.pdfapplication/pdf
And many more...

Common Patterns

1. Serve Entire Directories

// Serve everything in frontend/ and shared/ app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url)); app.get("/shared/*", (c) => serveFile(c.req.path, import.meta.url));

2. Serve Index Page with Data Injection

import { readFile } from "https://esm.town/v/std/utils/index.ts"; app.get("/", async (c) => { // Read the HTML file manually to modify it let html = await readFile("/frontend/index.html", import.meta.url); // Inject initial data to avoid extra round-trips const initialData = await fetchInitialData(); const dataScript = `<script> window.__INITIAL_DATA__ = ${JSON.stringify(initialData)}; </script>`; html = html.replace("</head>", `${dataScript}</head>`); return c.html(html); });

3. Fallback to Index for SPA Routing

app.get("*", async (c) => { // Try to serve the requested file first try { return await serveFile(c.req.path, import.meta.url); } catch { // If file doesn't exist, serve index.html (for client-side routing) return serveFile("/frontend/index.html", import.meta.url); } });

4. Serve Static Assets with Cache Headers

app.get("/assets/*", async (c) => { const response = await serveFile(c.req.path, import.meta.url); // Add cache headers for static assets response.headers.set("Cache-Control", "public, max-age=31536000"); return response; });

How It Works Internally

When you call serveFile(c.req.path, import.meta.url):

  1. Path Resolution: Uses import.meta.url to locate your project
  2. File Reading: Reads the file at the specified path
  3. Content-Type Detection: Analyzes file extension to set proper MIME type
  4. Response Creation: Returns a Response object with the file contents and headers
  5. Error Handling: Returns appropriate HTTP errors if file doesn't exist

Important Notes

  • Always use import.meta.url as the second parameter
  • File paths start with / (e.g., "/frontend/index.html", not "frontend/index.html")
  • Works with any file type that Val Town supports (text-based files)
  • Automatic error handling - returns 404 if file doesn't exist
  • No caching by default - add your own cache headers if needed

vs. Hono's serveStatic

āŒ DON'T use Hono's serveStatic middleware in Val Town:

// This doesn't work in Val Town import { serveStatic } from 'https://esm.sh/hono/middleware';

āœ… DO use Val Town's serveFile utility:

// This works perfectly in Val Town import { serveFile } from "https://esm.town/v/std/utils/index.ts";
  • readFile() - Read file contents as a string (useful for modifying before serving)
  • listFiles() - List all files in the project