Mizu can serve static files such as images, CSS, JavaScript, and other assets. This is essential for web applications that need to deliver frontend resources alongside API responses.
Overview
Static file serving in Mizu supports multiple file sources:
Source Use Case Method Local Directory Development, user uploads http.Dir()Embedded Files Single binary deployment embed.FS with http.FS()Memory Generated content, testing Custom http.FileSystem
Serving from a Local Directory
The simplest approach is serving files from a folder on disk:
package main
import (
" net/http "
" github.com/go-mizu/mizu "
)
func main () {
app := mizu . New ()
// Serve files from the "public" folder at the "/assets/" URL path
app . Static ( "/assets/" , http . Dir ( "public" ))
app . Listen ( ":3000" )
}
With this configuration:
File on Disk URL public/logo.pnghttp://localhost:3000/assets/logo.pngpublic/css/style.csshttp://localhost:3000/assets/css/style.csspublic/js/app.jshttp://localhost:3000/assets/js/app.js
Directory Structure Example
myapp/
├── main.go
├── public/
│ ├── index.html
│ ├── favicon.ico
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
Serving Embedded Files
For production deployments, embed static files directly into your Go binary using the embed package. This creates a single executable with no external file dependencies.
package main
import (
" embed "
" io/fs "
" net/http "
" github.com/go-mizu/mizu "
)
//go:embed public/*
var publicFS embed . FS
func main () {
app := mizu . New ()
// Remove the "public" prefix from embedded paths
sub , err := fs . Sub ( publicFS , "public" )
if err != nil {
panic ( err )
}
app . Static ( "/assets/" , http . FS ( sub ))
app . Listen ( ":3000" )
}
Understanding embed.FS
The //go:embed directive includes files at compile time:
//go:embed public/* // All files in public/
//go:embed public/**/* // All files recursively
//go:embed public/*.css // Only CSS files
//go:embed static images // Multiple directories
The embed package preserves directory structure. Use fs.Sub() to remove the root directory prefix when serving.
SPA (Single Page Application) Support
Single page applications require all routes to return index.html so the frontend router can handle navigation.
//go:embed dist/*
var distFS embed . FS
func main () {
app := mizu . New ()
// API routes first
api := app . Group ( "/api" )
api . Get ( "/users" , getUsers )
// SPA fallback handler
sub , _ := fs . Sub ( distFS , "dist" )
fileServer := http . FileServer ( http . FS ( sub ))
app . Get ( "/{path...}" , func ( c * mizu . Ctx ) error {
path := c . Param ( "path" )
// Try to serve the actual file first
f , err := sub . Open ( path )
if err == nil {
f . Close ()
fileServer . ServeHTTP ( c . Writer (), c . Request ())
return nil
}
// Fall back to index.html for SPA routing
c . Request (). URL . Path = "/"
fileServer . ServeHTTP ( c . Writer (), c . Request ())
return nil
})
app . Listen ( ":3000" )
}
Caching
Configure caching based on file types:
func cacheMiddleware ( next mizu . Handler ) mizu . Handler {
return func ( c * mizu . Ctx ) error {
path := c . Request (). URL . Path
switch {
case strings . HasSuffix ( path , ".html" ):
// HTML: no cache (always fresh)
c . Header (). Set ( "Cache-Control" , "no-cache, no-store, must-revalidate" )
case strings . HasSuffix ( path , ".css" ),
strings . HasSuffix ( path , ".js" ):
// CSS/JS: cache for 1 year (use versioning)
c . Header (). Set ( "Cache-Control" , "public, max-age=31536000, immutable" )
case strings . HasSuffix ( path , ".png" ),
strings . HasSuffix ( path , ".jpg" ):
// Images: cache for 1 month
c . Header (). Set ( "Cache-Control" , "public, max-age=2592000" )
default :
// Other files: cache for 1 day
c . Header (). Set ( "Cache-Control" , "public, max-age=86400" )
}
return next ( c )
}
}
Development vs Production
func main () {
app := mizu . New ()
if os . Getenv ( "ENV" ) == "development" {
// Serve from disk (hot reload)
app . Static ( "/assets/" , http . Dir ( "public" ))
} else {
// Serve from embedded (production)
sub , _ := fs . Sub ( publicFS , "public" )
app . Static ( "/assets/" , http . FS ( sub ))
}
app . Listen ( ":3000" )
}
Benefits of embedding:
Single binary deployment
No missing file issues
Faster startup (files already in memory)
Immutable content (great for containers)
Summary
Method Description app.Static(prefix, fs)Serve files from filesystem http.Dir(path)Create filesystem from local directory http.FS(fs)Create filesystem from embed.FS fs.Sub(fs, dir)Get subdirectory from filesystem app.Mount(path, handler)Mount any http.Handler
Next steps
Frontend Integration Serve React, Vue, and other SPAs.
Deployment Production deployment strategies.